package main import ( "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "strings" "text/template" "time" "gopkg.in/yaml.v2" ) // runSwagInit runs the swag init command to generate swagger docs func runSwagInit() error { cmd := exec.Command("swag", "init", "-g", "../../cmd/api/main.go", "--parseDependency", "--parseInternal") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } // ServiceConfig represents the main configuration structure type ServiceConfig struct { Services map[string]Service `yaml:"services"` Global GlobalConfig `yaml:"global,omitempty"` } // GlobalConfig contains global configuration type GlobalConfig struct { ModuleName string `yaml:"module_name"` OutputDir string `yaml:"output_dir"` PackagePrefix string `yaml:"package_prefix"` EnableSwagger bool `yaml:"enable_swagger"` EnableLogging bool `yaml:"enable_logging"` EnableMetrics bool `yaml:"enable_metrics"` } // Service represents individual service configuration type Service struct { Name string `yaml:"name"` Category string `yaml:"category"` Package string `yaml:"package"` SubPackage string `yaml:"sub_package"` Description string `yaml:"description"` BaseURL string `yaml:"base_url"` Timeout int `yaml:"timeout"` RetryCount int `yaml:"retry_count"` Endpoints map[string]SubEndpoints `yaml:"endpoints"` Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string } // Endpoint represents endpoint configuration type Endpoint struct { Methods []string `yaml:"methods"` GetPath string `yaml:"get_path,omitempty"` PostPath string `yaml:"post_path,omitempty"` PutPath string `yaml:"put_path,omitempty"` DeletePath string `yaml:"delete_path,omitempty"` PatchPath string `yaml:"patch_path,omitempty"` Model string `yaml:"model"` ResponseModel string `yaml:"response_model"` Description string `yaml:"description"` Summary string `yaml:"summary"` Tags []string `yaml:"tags"` RequireAuth bool `yaml:"require_auth"` RateLimit int `yaml:"rate_limit,omitempty"` CacheEnabled bool `yaml:"cache_enabled"` CacheTTL int `yaml:"cache_ttl,omitempty"` CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` } // SubEndpoints represents nested endpoint configuration for tree structure type SubEndpoints map[string]Endpoint // TemplateData holds data for generating handlers type TemplateData struct { ServiceName string ServiceLower string ServiceUpper string Category string Package string Description string BaseURL string Timeout int RetryCount int Endpoints []EndpointData Timestamp string ModuleName string HasValidator bool HasLogger bool HasMetrics bool HasSwagger bool HasAuth bool HasCache bool Dependencies []string // FIXED: Changed to []string Middleware []string // FIXED: Changed to []string GlobalConfig GlobalConfig } // EndpointData represents processed endpoint data type EndpointData struct { Name string NameLower string NameUpper string NameCamel string Methods []string GetPath string PostPath string PutPath string DeletePath string PatchPath string Model string ResponseModel string DataModel string // Data model name (e.g., PesertaData) Description string Summary string Tags []string HasGet bool HasPost bool HasPut bool HasDelete bool HasPatch bool RequireAuth bool RateLimit int CacheEnabled bool CacheTTL int PathParams []string QueryParams []string RequiredFields []string OptionalFields []string CustomHeaders map[string]string ModelPackage string // Package name for the model (peserta, sep, etc.) } // Updated template for merged handler structure const handlerTemplate = ` // Service: {{.ServiceName}} ({{.Category}}) // Description: {{.Description}} package {{.Package}} import ( "context" "encoding/json" "strings" "net/http" "time" "{{.ModuleName}}/internal/config" "{{.ModuleName}}/internal/models" "{{.ModuleName}}/internal/models/vclaim/{{.Package}}" "{{.ModuleName}}/internal/services/bpjs" "{{.ModuleName}}/pkg/logger" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "github.com/google/uuid" ) // {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services type {{.ServiceName}}Handler struct { service services.{{.ServiceName}}Service validator *validator.Validate logger logger.Logger config config.BpjsConfig } // {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler type {{.ServiceName}}HandlerConfig struct { BpjsConfig config.BpjsConfig Logger logger.Logger Validator *validator.Validate } // New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { return &{{.ServiceName}}Handler{ service: services.NewService(cfg.BpjsConfig), validator: cfg.Validator, logger: cfg.Logger, config: cfg.BpjsConfig, } } {{range .Endpoints}} {{if .HasGet}} // Get{{.Name}} godoc // @Summary Get {{.Name}} data // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} // @Param {{.}} path string true "{{.}}" example("example_value") {{end}} // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" // @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" // @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" // @Router {{.GetPath}} [get] func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() // Generate request ID if not present requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() c.Header("X-Request-ID", requestID) } {{if $.HasLogger}} h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.GetPath}}", {{range .PathParams}} "{{.}}": c.Param("{{.}}"), {{end}} }) {{end}} // Extract path parameters {{range .PathParams}} {{.}} := c.Param("{{.}}") if {{.}} == "" { {{if $.HasLogger}} h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Missing required parameter {{.}}", RequestID: requestID, }) return } {{end}} // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} {{if .PathParams}} endpoint := "{{.GetPath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} rawResponse, err := h.service.GetRawResponse(ctx, endpoint) {{else}} rawResponse, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map raw response to structured data var data {{.ModelPackage}}.{{.DataModel}} if err := json.Unmarshal(rawResponse, &data); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Failed to process response data", RequestID: requestID, }) return } // Set response data response.Data = data response.Status = "success" response.RequestID = requestID c.JSON(http.StatusOK, response) } {{end}} {{if .HasPost}} // Create{{.Name}} godoc // @Summary Create new {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" // @Param request body {{.ModelPackage}}.{{.Model}} true "{{.Name}} data" // @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid request body or validation error" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" // @Failure 409 {object} models.ErrorResponseBpjs "Conflict - {{.Name}} already exists" // @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" // @Router {{.PostPath}} [post] func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() c.Header("X-Request-ID", requestID) } {{if $.HasLogger}} h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ "request_id": requestID, }) {{end}} var req {{.ModelPackage}}.{{.Model}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Invalid request body", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Invalid request body: " + err.Error(), RequestID: requestID, }) return } // Validate request if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Validation failed", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Validation failed: " + err.Error(), RequestID: requestID, }) return } // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} rawResponse, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", &req) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map raw response to structured data var data {{.ModelPackage}}.{{.DataModel}} if err := json.Unmarshal(rawResponse, &data); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Failed to process response data", RequestID: requestID, }) return } // Set response data response.Data = data response.Status = "success" response.RequestID = requestID c.JSON(http.StatusCreated, response) } {{end}} {{if .HasPut}} // Update{{.Name}} godoc // @Summary Update existing {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} // @Param {{.}} path string true "{{.}}" example("example_value") {{end}} // @Param request body {{.ModelPackage}}.{{.Model}} true "{{.Name}} data" // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters or request body" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" // @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" // @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" // @Router {{.PutPath}} [put] func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() c.Header("X-Request-ID", requestID) } {{if $.HasLogger}} h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ "request_id": requestID, }) {{end}} {{range .PathParams}} {{.}} := c.Param("{{.}}") if {{.}} == "" { {{if $.HasLogger}} h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Missing required parameter {{.}}", RequestID: requestID, }) return } {{end}} var req {{.ModelPackage}}.{{.Model}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Invalid request body", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Invalid request body: " + err.Error(), RequestID: requestID, }) return } // Validate request if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Validation failed", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Validation failed: " + err.Error(), RequestID: requestID, }) return } // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} {{if .PathParams}} endpoint := "{{.PutPath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} rawResponse, err := h.service.PutRawResponse(ctx, endpoint, &req) {{else}} rawResponse, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", &req) {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map raw response to structured data var data {{.ModelPackage}}.{{.DataModel}} if err := json.Unmarshal(rawResponse, &data); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Failed to process response data", RequestID: requestID, }) return } // Set response data response.Data = data response.Status = "success" response.RequestID = requestID c.JSON(http.StatusOK, response) } {{end}} {{if .HasDelete}} // Delete{{.Name}} godoc // @Summary Delete existing {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} // @Param {{.}} path string true "{{.}}" example("example_value") {{end}} // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" // @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" // @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" // @Router {{.DeletePath}} [delete] func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() c.Header("X-Request-ID", requestID) } {{if $.HasLogger}} h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ "request_id": requestID, }) {{end}} {{range .PathParams}} {{.}} := c.Param("{{.}}") if {{.}} == "" { {{if $.HasLogger}} h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ Status: "error", Message: "Missing required parameter {{.}}", RequestID: requestID, }) return } {{end}} // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} {{if .PathParams}} endpoint := "{{.DeletePath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} rawResponse, err := h.service.DeleteRawResponse(ctx, endpoint) {{else}} rawResponse, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map raw response to structured data var data {{.ModelPackage}}.{{.DataModel}} if err := json.Unmarshal(rawResponse, &data); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Failed to process response data", RequestID: requestID, }) return } // Set response data response.Data = data response.Status = "success" response.RequestID = requestID c.JSON(http.StatusOK, response) } {{end}} {{end}} ` // Template for generating routes const routesTemplate = ` package v1 import ( "{{.ModuleName}}/internal/config" "{{.ModuleName}}/internal/middleware" "{{.ModuleName}}/internal/handlers/{{.Category}}/{{.Package}}" "github.com/gin-gonic/gin" ) func Register{{.ServiceName}}Routes(router *gin.RouterGroup, cfg *config.Config) { handler := {{.Package}}.New{{.ServiceName}}Handler({{.Package}}.{{.ServiceName}}HandlerConfig{ BpjsConfig: cfg.Bpjs, Logger: *logger.Default(), Validator: nil, }) group := router.Group("/{{.ServiceLower}}") {{range .Endpoints}} {{if .HasGet}} group.GET("{{.GetPath}}", handler.Get{{.Name}}) {{end}} {{if .HasPost}} group.POST("{{.PostPath}}", handler.Create{{.Name}}) {{end}} {{if .HasPut}} group.PUT("{{.PutPath}}", handler.Update{{.Name}}) {{end}} {{if .HasDelete}} group.DELETE("{{.DeletePath}}", handler.Delete{{.Name}}) {{end}} {{if .HasPatch}} group.PATCH("{{.PatchPath}}", handler.Patch{{.Name}}) {{end}} {{end}} } ` func main() { if len(os.Args) < 2 { printUsage() os.Exit(1) } configFile := os.Args[1] var targetService string if len(os.Args) > 2 { targetService = os.Args[2] } // Load configuration config, err := loadConfig(configFile) if err != nil { fmt.Printf("āŒ Error loading config: %v\n", err) os.Exit(1) } fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation...") fmt.Printf("šŸ“ Config file: %s\n", configFile) if targetService != "" { fmt.Printf("šŸŽÆ Target service: %s\n", targetService) } generated := 0 errors := 0 // Generate handlers for serviceName, service := range config.Services { if targetService != "" && serviceName != targetService { continue } fmt.Printf("šŸ”§ Generating handler for service: %s (%s)\n", service.Name, service.Category) err := generateHandler(serviceName, service, config.Global) if err != nil { fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) errors++ continue } fmt.Printf("āœ… Generated handler: %s.go\n", strings.ToLower(serviceName)) // Generate routes for this service err = generateRoutes(serviceName, service, config.Global) if err != nil { fmt.Printf("āŒ Error generating routes for %s: %v\n", serviceName, err) errors++ continue } fmt.Printf("āœ… Generated routes: %s_routes.go\n", strings.ToLower(serviceName)) generated++ } // Summary fmt.Println("\nšŸ“Š Generation Summary:") fmt.Printf(" āœ… Successfully generated: %d handlers and routes\n", generated) if errors > 0 { fmt.Printf(" āŒ Failed: %d handlers/routes\n", errors) } if generated > 0 { fmt.Println("šŸŽ‰ Generation completed successfully!") // Generate Swagger documentation if enabled // if config.Global.EnableSwagger { // fmt.Println("šŸ“š Generating Swagger documentation...") // if err := runSwagInit(); err != nil { // fmt.Printf("āš ļø Warning: Failed to generate Swagger docs: %v\n", err) // } else { // fmt.Println("āœ… Swagger documentation generated successfully!") // } // } } } func printUsage() { fmt.Println("BPJS Dynamic Handler Generator") fmt.Println() fmt.Println("Usage:") fmt.Println(" go run generate-dynamic-handler.go [service-name]") fmt.Println() fmt.Println("Examples:") fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") } func loadConfig(filename string) (*ServiceConfig, error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } var config ServiceConfig err = yaml.Unmarshal(data, &config) if err != nil { return nil, fmt.Errorf("failed to parse YAML config: %w", err) } // Set default values if config.Global.ModuleName == "" { config.Global.ModuleName = "api-service" } if config.Global.OutputDir == "" { config.Global.OutputDir = "internal/handlers" } return &config, nil } func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { // Create template with custom functions tmpl := template.New("handler").Funcs(template.FuncMap{ "contains": strings.Contains, "join": strings.Join, "title": strings.Title, "trimPrefix": strings.TrimPrefix, }) tmpl, err := tmpl.Parse(handlerTemplate) if err != nil { return fmt.Errorf("failed to parse template: %w", err) } outputDir := globalConfig.OutputDir if outputDir == "" { outputDir = "internal/handlers" } generatedFiles := 0 errorsCount := 0 // Loop over each module group in service.Endpoints for groupName, subEndpoints := range service.Endpoints { // Prepare template data for this group templateData := TemplateData{ ServiceName: service.Name, ServiceLower: strings.ToLower(service.Name), ServiceUpper: strings.ToUpper(service.Name), Category: service.Category, Package: groupName, // package name is group name Description: service.Description, BaseURL: service.BaseURL, Timeout: getOrDefault(service.Timeout, 30), RetryCount: getOrDefault(service.RetryCount, 3), Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: globalConfig.ModuleName, HasValidator: true, HasLogger: globalConfig.EnableLogging, HasMetrics: globalConfig.EnableMetrics, HasSwagger: globalConfig.EnableSwagger, Dependencies: service.Dependencies, Middleware: service.Middleware, GlobalConfig: globalConfig, } // Check for advanced features in this group for _, endpoint := range subEndpoints { if endpoint.RequireAuth { templateData.HasAuth = true } if endpoint.CacheEnabled { templateData.HasCache = true } } // Process endpoints for this group for subEndpointName, endpoint := range subEndpoints { fullEndpointName := groupName if subEndpointName != "" { fullEndpointName = fullEndpointName + strings.Title(subEndpointName) } endpointData := processEndpoint(fullEndpointName, endpoint, groupName) templateData.Endpoints = append(templateData.Endpoints, endpointData) } // Create output directory for this group: outputDir/service.Package/groupName groupOutputDir := filepath.Join(outputDir, service.Package, groupName) if err := os.MkdirAll(groupOutputDir, 0755); err != nil { errorsCount++ fmt.Printf("āŒ Failed to create directory %s: %v\n", groupOutputDir, err) continue } // Generate handler file for this group filename := filepath.Join(groupOutputDir, fmt.Sprintf("%s.go", groupName)) // Check if file already exists, skip generation if so if _, err := os.Stat(filename); err == nil { fmt.Printf("āš ļø Skipping generation for existing file: %s\n", filename) continue } file, err := os.Create(filename) if err != nil { errorsCount++ fmt.Printf("āŒ Failed to create file %s: %v\n", filename, err) continue } err = tmpl.Execute(file, templateData) file.Close() if err != nil { errorsCount++ fmt.Printf("āŒ Failed to execute template for %s: %v\n", filename, err) continue } fmt.Printf("āœ… Generated handler: %s\n", filename) generatedFiles++ } if errorsCount > 0 { return fmt.Errorf("generation completed with %d errors", errorsCount) } if generatedFiles == 0 { return fmt.Errorf("no handlers generated") } return nil } func generateRoutes(serviceName string, service Service, globalConfig GlobalConfig) error { // Read the main routes.go file routesFilePath := "internal/routes/v1/routes.go" routesContent, err := ioutil.ReadFile(routesFilePath) if err != nil { return fmt.Errorf("failed to read routes file: %w", err) } routesContentStr := string(routesContent) // Check if routes are already registered if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", service.Name)) { fmt.Printf("āš ļø Routes for %s already registered in main routes file\n", service.Name) return nil } // Prepare template data for all groups var allRoutes []string // Loop over each module group in service.Endpoints for groupName, subEndpoints := range service.Endpoints { // Prepare template data for this group templateData := TemplateData{ ServiceName: service.Name, ServiceLower: strings.ToLower(service.Name), ServiceUpper: strings.ToUpper(service.Name), Category: service.Category, Package: groupName, // package name is group name Description: service.Description, BaseURL: service.BaseURL, Timeout: getOrDefault(service.Timeout, 30), RetryCount: getOrDefault(service.RetryCount, 3), Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: globalConfig.ModuleName, HasValidator: true, HasLogger: globalConfig.EnableLogging, HasMetrics: globalConfig.EnableMetrics, HasSwagger: globalConfig.EnableSwagger, Dependencies: service.Dependencies, Middleware: service.Middleware, GlobalConfig: globalConfig, } // Process endpoints for this group for subEndpointName, endpoint := range subEndpoints { fullEndpointName := groupName if subEndpointName != "" { fullEndpointName = fullEndpointName + strings.Title(subEndpointName) } endpointData := processEndpoint(fullEndpointName, endpoint, groupName) templateData.Endpoints = append(templateData.Endpoints, endpointData) } // Generate routes code for this group var routesCode strings.Builder routesCode.WriteString(fmt.Sprintf("\n\t// %s routes\n", strings.Title(groupName))) routesCode.WriteString(fmt.Sprintf("\t%sHandler := %s.New%sHandler(%s.%sHandlerConfig{\n", groupName, groupName, service.Name, groupName, service.Name)) routesCode.WriteString("\t\tBpjsConfig: cfg.Bpjs,\n") routesCode.WriteString("\t\tLogger: *logger.Default(),\n") routesCode.WriteString("\t\tValidator: nil,\n") routesCode.WriteString("\t})\n") routesCode.WriteString(fmt.Sprintf("\t%sGroup := v1.Group(\"/%s\")\n", groupName, groupName)) for _, endpoint := range templateData.Endpoints { if endpoint.HasGet { routesCode.WriteString(fmt.Sprintf("\t%sGroup.GET(\"%s\", %sHandler.Get%s)\n", groupName, endpoint.GetPath, groupName, endpoint.Name)) } if endpoint.HasPost { routesCode.WriteString(fmt.Sprintf("\t%sGroup.POST(\"%s\", %sHandler.Create%s)\n", groupName, endpoint.PostPath, groupName, endpoint.Name)) } if endpoint.HasPut { routesCode.WriteString(fmt.Sprintf("\t%sGroup.PUT(\"%s\", %sHandler.Update%s)\n", groupName, endpoint.PutPath, groupName, endpoint.Name)) } if endpoint.HasDelete { routesCode.WriteString(fmt.Sprintf("\t%sGroup.DELETE(\"%s\", %sHandler.Delete%s)\n", groupName, endpoint.DeletePath, groupName, endpoint.Name)) } if endpoint.HasPatch { routesCode.WriteString(fmt.Sprintf("\t%sGroup.PATCH(\"%s\", %sHandler.Patch%s)\n", groupName, endpoint.PatchPath, groupName, endpoint.Name)) } } allRoutes = append(allRoutes, routesCode.String()) } // Find the PUBLISHED ROUTES section and insert the routes publishedRoutesMarker := "// ============= PUBLISHED ROUTES ===============================================" if !strings.Contains(routesContentStr, publishedRoutesMarker) { return fmt.Errorf("PUBLISHED ROUTES marker not found in routes.go") } // Insert the routes after the marker insertionPoint := strings.Index(routesContentStr, publishedRoutesMarker) + len(publishedRoutesMarker) newRoutesContent := routesContentStr[:insertionPoint] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[insertionPoint:] // Write back the modified routes file err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) if err != nil { return fmt.Errorf("failed to write updated routes file: %w", err) } fmt.Printf("āœ… Updated main routes file with %s routes\n", service.Name) return nil } func processEndpoint(name string, endpoint Endpoint, endpointGroup string) EndpointData { data := EndpointData{ Name: strings.Title(name), NameLower: strings.ToLower(name), NameUpper: strings.ToUpper(name), NameCamel: toCamelCase(name), Methods: endpoint.Methods, GetPath: endpoint.GetPath, PostPath: endpoint.PostPath, PutPath: endpoint.PutPath, DeletePath: endpoint.DeletePath, PatchPath: endpoint.PatchPath, Model: endpoint.Model, ResponseModel: endpoint.ResponseModel, DataModel: strings.Replace(endpoint.ResponseModel, "Response", "Data", 1), Description: endpoint.Description, Summary: endpoint.Summary, Tags: endpoint.Tags, RequireAuth: endpoint.RequireAuth, RateLimit: endpoint.RateLimit, CacheEnabled: endpoint.CacheEnabled, CacheTTL: getOrDefault(endpoint.CacheTTL, 300), CustomHeaders: endpoint.CustomHeaders, ModelPackage: endpointGroup, // Set the model package based on endpoint group } // Set method flags and extract path parameters for _, method := range endpoint.Methods { switch strings.ToUpper(method) { case "GET": data.HasGet = true data.PathParams = extractPathParams(endpoint.GetPath) case "POST": data.HasPost = true case "PUT": data.HasPut = true data.PathParams = extractPathParams(endpoint.PutPath) case "DELETE": data.HasDelete = true data.PathParams = extractPathParams(endpoint.DeletePath) case "PATCH": data.HasPatch = true data.PathParams = extractPathParams(endpoint.PatchPath) } } return data } func extractPathParams(path string) []string { if path == "" { return nil } var params []string parts := strings.Split(path, "/") for _, part := range parts { if strings.HasPrefix(part, ":") { params = append(params, strings.TrimPrefix(part, ":")) } } return params } func toCamelCase(str string) string { words := strings.FieldsFunc(str, func(c rune) bool { return c == '_' || c == '-' || c == ' ' }) if len(words) == 0 { return str } result := strings.ToLower(words[0]) for _, word := range words[1:] { result += strings.Title(strings.ToLower(word)) } return result } func getOrDefault(value, defaultValue int) int { if value == 0 { return defaultValue } return value }