package main import ( "fmt" "go/ast" "go/parser" "go/token" "io/ioutil" "os" "path/filepath" "regexp" "strings" "text/template" "time" "gopkg.in/yaml.v2" ) // Enhanced structures untuk validasi type HandlerValidation struct { ExistingFunctions map[string]bool NewFunctions []string UpdatedFiles []string CreatedFiles []string } type DirectoryInfo struct { Path string IsFile bool Functions []FunctionInfo Children map[string]*DirectoryInfo } type FunctionInfo struct { Name string Methods []string Endpoint string Config EndpointConfig } type EndpointConfig struct { Methods []string `yaml:"methods"` GetPath string `yaml:"get_path"` PostPath string `yaml:"post_path"` PutPath string `yaml:"put_path"` DeletePath string `yaml:"delete_path"` Model string `yaml:"model"` ResponseModel string `yaml:"response_model"` RequestModel string `yaml:"request_model"` Description string `yaml:"description"` Summary string `yaml:"summary"` Tags []string `yaml:"tags"` RequireAuth bool `yaml:"require_auth"` CacheEnabled bool `yaml:"cache_enabled"` CacheTTL int `yaml:"cache_ttl"` } type GlobalConfig struct { ModuleName string `yaml:"module_name"` OutputDir string `yaml:"output_dir"` EnableSwagger bool `yaml:"enable_swagger"` EnableLogging bool `yaml:"enable_logging"` } type ServiceConfig struct { Global GlobalConfig `yaml:"global,omitempty"` Services map[string]Service `yaml:"services"` } type Service struct { Name string `yaml:"name"` Category string `yaml:"category"` Package string `yaml:"package"` Description string `yaml:"description"` BaseURL string `yaml:"base_url"` Timeout int `yaml:"timeout"` RetryCount int `yaml:"retry_count"` Endpoints map[string]EndpointGroup `yaml:"endpoints"` Dependencies string `yaml:"dependencies,omitempty"` Middleware string `yaml:"middleware,omitempty"` } type EndpointGroup struct { Description string `yaml:"description"` HandlerFolder string `yaml:"handler_folder"` HandlerFile string `yaml:"handler_file"` Functions map[string]FunctionConfig `yaml:"functions"` } type FunctionConfig struct { Methods []string `yaml:"methods"` Path string `yaml:"path"` Model string `yaml:"model"` ResponseModel string `yaml:"response_model"` RequestModel string `yaml:"request_model"` Description string `yaml:"description"` Summary string `yaml:"summary"` Tags []string `yaml:"tags"` RequireAuth bool `yaml:"require_auth"` CacheEnabled bool `yaml:"cache_enabled"` CacheTTL int `yaml:"cache_ttl"` } 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 HasSwagger bool GlobalConfig GlobalConfig } type EndpointData struct { Name string NameLower string NameUpper string NameCamel string Methods []string GetPath string PostPath string PutPath string DeletePath string Model string ResponseModel string RequestModel string DataModel string Description string Summary string Tags []string HasGet bool HasPost bool HasPut bool HasDelete bool RequireAuth bool CacheEnabled bool CacheTTL int PathParams []string ModelPackage string } // Fungsi utama untuk generate handler dengan validasi func generateHandlerWithValidation(serviceName string, svc Service, gc GlobalConfig) error { baseDir := gc.OutputDir for _, grp := range svc.Endpoints { folder := filepath.Join(baseDir, grp.HandlerFolder) if err := os.MkdirAll(folder, 0755); err != nil { return fmt.Errorf("mkdir %s: %w", folder, err) } filePath := filepath.Join(folder, grp.HandlerFile) // Check if file exists fileExists := false if _, err := os.Stat(filePath); err == nil { fileExists = true } if !fileExists { // Create new file with full template err := createNewHandlerFileFromConfig(filePath, svc, grp, gc) if err != nil { return fmt.Errorf("create file %s: %w", filePath, err) } fmt.Printf("✅ Created new file: %s\n", filePath) } else { // Update existing file with functions only err := updateExistingHandlerFile(filePath, svc, grp, gc) if err != nil { return fmt.Errorf("update file %s: %w", filePath, err) } fmt.Printf("✅ Updated existing file: %s\n", filePath) } } return nil } // Create new handler file with full template func createNewHandlerFileFromConfig(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig) error { // Collect all functions for this group var allEndpoints []EndpointData for fname, fcfg := range grp.Functions { td := processFunctionData(svc, grp, fname, fcfg, gc) allEndpoints = append(allEndpoints, td.Endpoints...) } templateData := TemplateData{ ServiceName: svc.Name, ServiceLower: strings.ToLower(svc.Name), ServiceUpper: strings.ToUpper(svc.Name), Category: svc.Category, Package: grp.HandlerFolder, Description: svc.Description, BaseURL: svc.BaseURL, Timeout: svc.Timeout, RetryCount: svc.RetryCount, Endpoints: allEndpoints, Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: gc.ModuleName, HasValidator: true, HasLogger: gc.EnableLogging, HasSwagger: gc.EnableSwagger, GlobalConfig: gc, } return createNewHandlerFile(filePath, templateData) } // Update existing handler file with functions only func updateExistingHandlerFile(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig) error { // Read existing content existingContent, err := ioutil.ReadFile(filePath) if err != nil { return err } content := string(existingContent) // Check for existing functions and collect new ones var newEndpoints []EndpointData for fname, fcfg := range grp.Functions { // Check for all possible function names that could be generated functionExists := false for _, method := range fcfg.Methods { funcName := generateFunctionName(fname, method) sig := fmt.Sprintf("func (h *%sHandler) %s", svc.Name, funcName) if strings.Contains(content, sig) { fmt.Printf("⚠️ Skip existing: %s (%s)\n", fname, funcName) functionExists = true break } } if functionExists { continue } // siapkan data td := processFunctionData(svc, grp, fname, fcfg, gc) newEndpoints = append(newEndpoints, td.Endpoints...) fmt.Printf("✅ Will add: %s\n", fname) } if len(newEndpoints) == 0 { fmt.Printf("⏭️ No new functions to add\n") return nil } // Generate new functions using functions-only template templateData := TemplateData{ ServiceName: svc.Name, ServiceLower: strings.ToLower(svc.Name), ServiceUpper: strings.ToUpper(svc.Name), Category: svc.Category, Package: grp.HandlerFolder, Description: svc.Description, BaseURL: svc.BaseURL, Timeout: svc.Timeout, RetryCount: svc.RetryCount, Endpoints: newEndpoints, Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: gc.ModuleName, HasValidator: true, HasLogger: gc.EnableLogging, HasSwagger: gc.EnableSwagger, GlobalConfig: gc, } newFunctions, err := generateNewFunctionsOnly(templateData) if err != nil { return err } // Merge content mergedContent := mergeGoFileContent(content, newFunctions) // Write back return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) } // Helper toCamelCase func toCamelCase(s string) string { parts := strings.FieldsFunc(s, func(r rune) bool { return r == '_' || r == '-' || r == ' ' }) for i, p := range parts { parts[i] = strings.Title(strings.ToLower(p)) } return strings.Join(parts, "") } // Parse struktur direktori dari endpoints YAML func parseDirectoryStructure(endpoints map[string]interface{}, serviceName string) *DirectoryInfo { root := &DirectoryInfo{ Path: "", Children: make(map[string]*DirectoryInfo), } for key, value := range endpoints { parseNestedEndpoints(root, key, value) } return root } func parseNestedEndpoints(parent *DirectoryInfo, name string, value interface{}) { switch v := value.(type) { case map[string]interface{}: // Check if this contains direct endpoint config if isDirectEndpoint(v) { // This is a direct endpoint - create as file parent.Children[name] = &DirectoryInfo{ Path: name, IsFile: true, Functions: []FunctionInfo{parseEndpointToFunction(name, v)}, Children: make(map[string]*DirectoryInfo), } } else { // This is nested structure - create as directory or file child := &DirectoryInfo{ Path: name, IsFile: false, Functions: make([]FunctionInfo, 0), Children: make(map[string]*DirectoryInfo), } // Check if any direct children are endpoints hasDirectEndpoints := false for childName, childValue := range v { if childMap, ok := childValue.(map[string]interface{}); ok && isDirectEndpoint(childMap) { hasDirectEndpoints = true child.Functions = append(child.Functions, parseEndpointToFunction(childName, childMap)) } } if hasDirectEndpoints { child.IsFile = true } parent.Children[name] = child // Recursively parse nested children for childName, childValue := range v { if childMap, ok := childValue.(map[string]interface{}); ok && !isDirectEndpoint(childMap) { parseNestedEndpoints(child, childName, childValue) } } } } } func isDirectEndpoint(m map[string]interface{}) bool { _, hasMethods := m["methods"] _, hasGetPath := m["get_path"] _, hasPostPath := m["post_path"] return hasMethods || hasGetPath || hasPostPath } func parseEndpointToFunction(name string, config map[string]interface{}) FunctionInfo { function := FunctionInfo{ Name: name, Endpoint: name, Methods: make([]string, 0), } if methods, ok := config["methods"].([]interface{}); ok { for _, method := range methods { if methodStr, ok := method.(string); ok { function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(methodStr))) } } } else if methodStr, ok := config["methods"].(string); ok { // Handle case where methods is a string like "GET,POST" methods := strings.Split(methodStr, ",") for _, method := range methods { function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(method))) } } return function } // Process directory structure dan generate files func processDirectoryStructure(baseDir string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { for name, child := range dirInfo.Children { currentPath := filepath.Join(baseDir, child.Path) if child.IsFile { // Process as file err := processHandlerFile(currentPath, name, child, service, globalConfig, validation) if err != nil { return fmt.Errorf("failed to process file %s: %w", name, err) } } else { // Create directory dan process children if err := os.MkdirAll(currentPath, 0755); err != nil { return fmt.Errorf("failed to create directory %s: %w", currentPath, err) } err := processDirectoryStructure(currentPath, child, service, globalConfig, validation) if err != nil { return err } } } return nil } func processFunctionData(svc Service, grp EndpointGroup, fname string, fcfg FunctionConfig, gc GlobalConfig) TemplateData { ed := EndpointData{ Name: toCamelCase(fname), NameLower: strings.ToLower(fname), NameUpper: strings.ToUpper(fname), NameCamel: toCamelCase(fname), Methods: fcfg.Methods, GetPath: fcfg.Path, PostPath: fcfg.Path, PutPath: fcfg.Path, DeletePath: fcfg.Path, Model: fcfg.Model, ResponseModel: fcfg.ResponseModel, RequestModel: fcfg.RequestModel, DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1), Description: fcfg.Description, Summary: fcfg.Summary, Tags: fcfg.Tags, RequireAuth: fcfg.RequireAuth, CacheEnabled: fcfg.CacheEnabled, CacheTTL: fcfg.CacheTTL, PathParams: extractPathParams(fcfg.Path), ModelPackage: grp.HandlerFolder, } // set flags for _, m := range fcfg.Methods { switch strings.ToUpper(m) { case "GET": ed.HasGet = true case "POST": ed.HasPost = true case "PUT": ed.HasPut = true case "DELETE": ed.HasDelete = true } } return TemplateData{ ServiceName: svc.Name, ServiceLower: strings.ToLower(svc.Name), ServiceUpper: strings.ToUpper(svc.Name), Category: svc.Category, Package: grp.HandlerFolder, Description: svc.Description, BaseURL: svc.BaseURL, Timeout: svc.Timeout, RetryCount: svc.RetryCount, Endpoints: []EndpointData{ed}, Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: gc.ModuleName, HasValidator: true, HasLogger: gc.EnableLogging, HasSwagger: gc.EnableSwagger, GlobalConfig: gc, } } // extractPathParams (jika belum ada) func extractPathParams(path string) []string { var ps []string for _, part := range strings.Split(path, "/") { if strings.HasPrefix(part, ":") { ps = append(ps, strings.TrimPrefix(part, ":")) } } return ps } // Process individual handler file func processHandlerFile(basePath, fileName string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { filePath := filepath.Join(filepath.Dir(basePath), fmt.Sprintf("%s.go", fileName)) fmt.Printf("📄 Processing file: %s\n", filePath) // Check if file exists fileExists := false if _, err := os.Stat(filePath); err == nil { fileExists = true fmt.Printf(" 📋 File exists, checking functions...\n") } var existingFunctions map[string]bool if fileExists { // Parse existing functions functions, err := extractExistingFunctions(filePath) if err != nil { fmt.Printf(" ⚠️ Warning: Could not parse existing file: %v\n", err) existingFunctions = make(map[string]bool) } else { existingFunctions = functions fmt.Printf(" ✓ Found %d existing functions\n", len(functions)) } } else { existingFunctions = make(map[string]bool) } // Determine which functions to generate functionsToGenerate := make([]FunctionInfo, 0) for _, fnInfo := range dirInfo.Functions { for _, method := range fnInfo.Methods { functionName := generateFunctionName(fnInfo.Name, method) if !existingFunctions[functionName] { functionsToGenerate = append(functionsToGenerate, FunctionInfo{ Name: fnInfo.Name, Methods: []string{method}, Endpoint: fnInfo.Endpoint, }) validation.NewFunctions = append(validation.NewFunctions, functionName) fmt.Printf(" ✅ Will generate: %s\n", functionName) } else { fmt.Printf(" ⏭️ Already exists: %s\n", functionName) } } } // Generate file if needed if len(functionsToGenerate) > 0 { templateData := prepareTemplateData(fileName, service, globalConfig, functionsToGenerate) if fileExists { // Merge with existing file err := mergeWithExistingFile(filePath, templateData) if err != nil { return fmt.Errorf("failed to merge file %s: %w", filePath, err) } validation.UpdatedFiles = append(validation.UpdatedFiles, filePath) fmt.Printf(" 📝 Updated existing file\n") } else { // Create new file err := createNewHandlerFile(filePath, templateData) if err != nil { return fmt.Errorf("failed to create file %s: %w", filePath, err) } validation.CreatedFiles = append(validation.CreatedFiles, filePath) fmt.Printf(" 📁 Created new file\n") } } else if !fileExists { fmt.Printf(" ⏭️ No functions to generate and file doesn't exist\n") } else { fmt.Printf(" ⏭️ All functions already exist\n") } return nil } // Extract existing functions from Go file func extractExistingFunctions(filePath string) (map[string]bool, error) { fileSet := token.NewFileSet() node, err := parser.ParseFile(fileSet, filePath, nil, parser.ParseComments) if err != nil { return nil, err } functions := make(map[string]bool) ast.Inspect(node, func(n ast.Node) bool { if fn, ok := n.(*ast.FuncDecl); ok { if fn.Name != nil { functions[fn.Name.Name] = true } } return true }) return functions, nil } // Generate function name berdasarkan endpoint dan method func generateFunctionName(endpointName, method string) string { switch strings.ToUpper(method) { case "GET": return fmt.Sprintf("Get%s", strings.Title(endpointName)) case "POST": return fmt.Sprintf("Create%s", strings.Title(endpointName)) case "PUT": return fmt.Sprintf("Update%s", strings.Title(endpointName)) case "DELETE": return fmt.Sprintf("Delete%s", strings.Title(endpointName)) case "PATCH": return fmt.Sprintf("Patch%s", strings.Title(endpointName)) default: return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName)) } } // Prepare template data func prepareTemplateData(packageName string, service Service, globalConfig GlobalConfig, functions []FunctionInfo) TemplateData { endpoints := make([]EndpointData, 0) for _, fnInfo := range functions { endpoint := EndpointData{ Name: strings.Title(fnInfo.Name), NameLower: strings.ToLower(fnInfo.Name), NameUpper: strings.ToUpper(fnInfo.Name), NameCamel: toCamelCase(fnInfo.Name), ModelPackage: packageName, Model: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), ResponseModel: fmt.Sprintf("%sResponse", strings.Title(fnInfo.Name)), RequestModel: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), DataModel: fmt.Sprintf("%sData", strings.Title(fnInfo.Name)), Description: fmt.Sprintf("Handle %s operations", fnInfo.Name), Tags: []string{strings.Title(packageName)}, } // Set paths dan methods for _, method := range fnInfo.Methods { switch strings.ToUpper(method) { case "GET": endpoint.HasGet = true endpoint.GetPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) case "POST": endpoint.HasPost = true endpoint.PostPath = fmt.Sprintf("/%s", packageName) case "PUT": endpoint.HasPut = true endpoint.PutPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) case "DELETE": endpoint.HasDelete = true endpoint.DeletePath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) } } endpoints = append(endpoints, endpoint) } return TemplateData{ ServiceName: service.Name, ServiceLower: strings.ToLower(service.Name), ServiceUpper: strings.ToUpper(service.Name), Category: service.Category, Package: packageName, Description: service.Description, BaseURL: service.BaseURL, Timeout: getOrDefault(service.Timeout, 30), RetryCount: getOrDefault(service.RetryCount, 3), Endpoints: endpoints, Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: globalConfig.ModuleName, HasValidator: true, HasLogger: globalConfig.EnableLogging, HasSwagger: globalConfig.EnableSwagger, GlobalConfig: globalConfig, } } // Template untuk handler file lengkap (untuk file baru) const handlerTemplate = `// Package {{.Package}} handles {{.ServiceName}} BPJS services // Generated on: {{.Timestamp}} package handlers import ( "context" "encoding/json" "net/http" "strings" "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.VClaimService 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 {{index .Tags 0}} // @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}} resp, err := h.service.GetRawResponse(ctx, endpoint) {{else}} resp, 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 the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID c.JSON(http.StatusOK, response) } {{end}} {{if .HasPost}} // Create{{.Name}} godoc // @Summary Create new {{.Name}} // @Description Create new {{.Name}} in BPJS system // @Tags {{index .Tags 0}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" // @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" // @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" // @Failure 409 {object} models.ErrorResponseBpjs "Conflict" // @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() // 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 Create{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.PostPath}}", }) {{end}} // Bind and validate request body var req {{.ModelPackage}}.{{.RequestModel}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to bind 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 structure if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Request 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 } {{if .PathParams}} // 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}} endpoint := "{{.PostPath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} {{end}} // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} {{if .PathParams}} resp, err := h.service.PostRawResponse(ctx, endpoint, req) {{else}} resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} // Handle specific BPJS errors if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} already exists or conflict occurred", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusCreated, response) } {{end}} {{if .HasPut}} // Update{{.Name}} godoc // @Summary Update existing {{.Name}} // @Description Update existing {{.Name}} in BPJS system // @Tags {{index .Tags 0}} // @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}}.{{.RequestModel}} true "{{.Name}} update data" // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.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 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" // @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() // 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 Update{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.PutPath}}", {{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}} // Bind and validate request body var req {{.ModelPackage}}.{{.RequestModel}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to bind 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 structure if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Request 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}} resp, err := h.service.PutRawResponse(ctx, endpoint, req) {{else}} resp, 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}} // Handle specific BPJS errors if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} not found", RequestID: requestID, }) return } if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ Status: "error", Message: "Update conflict occurred", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusOK, response) } {{end}} {{if .HasDelete}} // Delete{{.Name}} godoc // @Summary Delete existing {{.Name}} // @Description Delete existing {{.Name}} from BPJS system // @Tags {{index .Tags 0}} // @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() // 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 Delete{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.DeletePath}}", {{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 := "{{.DeletePath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} resp, err := h.service.DeleteRawResponse(ctx, endpoint) {{else}} resp, 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}} // Handle specific BPJS errors if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} not found", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } else { // For delete operations, sometimes there's no data in response response.Data = nil } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusOK, response) } {{end}} {{end}}` // Template untuk menambah function saja (untuk file yang sudah ada) const functionsOnlyTemplate = `{{range .Endpoints}} {{if .HasGet}} // Get{{.Name}} godoc // @Summary Get {{.Name}} data // @Description {{.Description}} // @Tags {{index .Tags 0}} // @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}} resp, err := h.service.GetRawResponse(ctx, endpoint) {{else}} resp, 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 the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID c.JSON(http.StatusOK, response) } {{end}} {{if .HasPost}} // Create{{.Name}} godoc // @Summary Create new {{.Name}} // @Description Create new {{.Name}} in BPJS system // @Tags {{index .Tags 0}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" // @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" // @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" // @Failure 409 {object} models.ErrorResponseBpjs "Conflict" // @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() // 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 Create{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.PostPath}}", }) {{end}} // Bind and validate request body var req {{.ModelPackage}}.{{.RequestModel}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to bind 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 structure if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Request 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 } {{if .PathParams}} // 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}} endpoint := "{{.PostPath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} {{end}} // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} {{if .PathParams}} resp, err := h.service.PostRawResponse(ctx, endpoint, req) {{else}} resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} // Handle specific BPJS errors if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} already exists or conflict occurred", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusCreated, response) } {{end}} {{if .HasPut}} // Update{{.Name}} godoc // @Summary Update existing {{.Name}} // @Description Update existing {{.Name}} in BPJS system // @Tags {{index .Tags 0}} // @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}}.{{.RequestModel}} true "{{.Name}} update data" // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.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 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" // @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() // 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 Update{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.PutPath}}", {{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}} // Bind and validate request body var req {{.ModelPackage}}.{{.RequestModel}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to bind 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 structure if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Request 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}} resp, err := h.service.PutRawResponse(ctx, endpoint, req) {{else}} resp, 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}} // Handle specific BPJS errors if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} not found", RequestID: requestID, }) return } if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ Status: "error", Message: "Update conflict occurred", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusOK, response) } {{end}} {{if .HasDelete}} // Delete{{.Name}} godoc // @Summary Delete existing {{.Name}} // @Description Delete existing {{.Name}} from BPJS system // @Tags {{index .Tags 0}} // @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() // 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 Delete{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.DeletePath}}", {{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 := "{{.DeletePath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} resp, err := h.service.DeleteRawResponse(ctx, endpoint) {{else}} resp, 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}} // Handle specific BPJS errors if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} not found", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } else { // For delete operations, sometimes there's no data in response response.Data = nil } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusOK, response) } {{end}} {{end}}` // Create new handler file func createNewHandlerFile(filePath string, templateData TemplateData) error { // Create directory if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { return err } // Parse template tmpl := template.New("handler").Funcs(template.FuncMap{ "title": strings.Title, "index": func(slice []string, i int) string { if i >= 0 && i < len(slice) { return slice[i] } return "" }, }) tmpl, err := tmpl.Parse(handlerTemplate) if err != nil { return err } // Create file file, err := os.Create(filePath) if err != nil { return err } defer file.Close() // Execute template return tmpl.Execute(file, templateData) } // Merge dengan existing file func mergeWithExistingFile(filePath string, templateData TemplateData) error { // Read existing content existingContent, err := ioutil.ReadFile(filePath) if err != nil { return err } // Generate new functions newFunctions, err := generateNewFunctionsOnly(templateData) if err != nil { return err } // Merge content mergedContent := mergeGoFileContent(string(existingContent), newFunctions) // Write back return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) } func generateNewFunctionsOnly(templateData TemplateData) (string, error) { funcTemplate := ` {{range .Endpoints}} {{if .HasGet}} // Get{{.Name}} godoc // @Summary Get {{.Name}} data // @Description {{.Description}} // @Tags {{index .Tags 0}} // @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}} resp, err := h.service.GetRawResponse(ctx, endpoint) {{else}} resp, 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 the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID c.JSON(http.StatusOK, response) } {{end}} {{if .HasPost}} // Create{{.Name}} godoc // @Summary Create new {{.Name}} // @Description Create new {{.Name}} in BPJS system // @Tags {{index .Tags 0}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param X-Request-ID header string false "Request ID for tracking" // @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" // @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" // @Failure 409 {object} models.ErrorResponseBpjs "Conflict" // @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() // 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 Create{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.PostPath}}", }) {{end}} // Bind and validate request body var req {{.ModelPackage}}.{{.RequestModel}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to bind 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 structure if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Request 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 } {{if .PathParams}} // 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}} endpoint := "{{.PostPath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} {{end}} // Call service method var response {{.ModelPackage}}.{{.ResponseModel}} {{if .PathParams}} resp, err := h.service.PostRawResponse(ctx, endpoint, req) {{else}} resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} // Handle specific BPJS errors if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} already exists or conflict occurred", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusCreated, response) } {{end}} {{if .HasPut}} // Update{{.Name}} godoc // @Summary Update existing {{.Name}} // @Description Update existing {{.Name}} in BPJS system // @Tags {{index .Tags 0}} // @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}}.{{.RequestModel}} true "{{.Name}} update data" // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.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 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" // @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() // 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 Update{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.PutPath}}", {{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}} // Bind and validate request body var req {{.ModelPackage}}.{{.RequestModel}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Failed to bind 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 structure if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Request 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}} resp, err := h.service.PutRawResponse(ctx, endpoint, req) {{else}} resp, 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}} // Handle specific BPJS errors if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} not found", RequestID: requestID, }) return } if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ Status: "error", Message: "Update conflict occurred", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusOK, response) } {{end}} {{if .HasDelete}} // Delete{{.Name}} godoc // @Summary Delete existing {{.Name}} // @Description Delete existing {{.Name}} from BPJS system // @Tags {{index .Tags 0}} // @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() // 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 Delete{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.DeletePath}}", {{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 := "{{.DeletePath}}" {{range .PathParams}} endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) {{end}} resp, err := h.service.DeleteRawResponse(ctx, endpoint) {{else}} resp, 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}} // Handle specific BPJS errors if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ Status: "error", Message: "{{.Name}} not found", RequestID: requestID, }) return } c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Map the raw response response.MetaData = resp.MetaData if resp.Response != nil { response.Data = &{{.ModelPackage}}.{{.DataModel}}{} if respStr, ok := resp.Response.(string); ok { // Decrypt the response string consID, secretKey, _, tstamp, _ := h.config.SetHeader() decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to decrypt response", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} } else { json.Unmarshal([]byte(decryptedResp), response.Data) } } else if respMap, ok := resp.Response.(map[string]interface{}); ok { // Response is already unmarshaled JSON if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { dataBytes, _ := json.Marshal(dataMap) json.Unmarshal(dataBytes, response.Data) } else { // Try to unmarshal the whole response respBytes, _ := json.Marshal(resp.Response) json.Unmarshal(respBytes, response.Data) } } } else { // For delete operations, sometimes there's no data in response response.Data = nil } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID {{if $.HasLogger}} h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusOK, response) } {{end}} {{end}}` tmpl := template.New("functions") tmpl, err := tmpl.Parse(funcTemplate) if err != nil { return "", err } var result strings.Builder err = tmpl.Execute(&result, templateData) if err != nil { return "", err } // Remove duplicate function definitions by simple regex grouping // This is a simple approach to avoid duplicate functions in merged content funcRegex := regexp.MustCompile(`(?s)(func \(h \*\w+Handler\) Get\w+\(c \*gin.Context\) \{.*?\})`) matches := funcRegex.FindAllString(result.String(), -1) uniqueFuncs := make(map[string]bool) var uniqueResult strings.Builder for _, m := range matches { if !uniqueFuncs[m] { uniqueFuncs[m] = true uniqueResult.WriteString(m) uniqueResult.WriteString("\n\n") } } // If no matches found, return original result if uniqueResult.Len() == 0 { return result.String(), nil } return uniqueResult.String(), nil } func mergeGoFileContent(existingContent, newFunctions string) string { // Find last closing brace re := regexp.MustCompile(`}\s*$`) lastBraceIndex := re.FindStringIndex(existingContent) if lastBraceIndex == nil { return existingContent + "\n" + newFunctions } before := existingContent[:lastBraceIndex[0]] after := existingContent[lastBraceIndex[0]:] return before + "\n" + newFunctions + "\n" + after } // Main function 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] } 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 with Validation...") fmt.Printf("📁 Config file: %s\n", configFile) if targetService != "" { fmt.Printf("🎯 Target service: %s\n", targetService) } generated := 0 errors := 0 for serviceName, service := range config.Services { if targetService != "" && serviceName != targetService { continue } err := generateHandlerWithValidation(serviceName, service, config.Global) if err != nil { fmt.Printf("❌ Error generating handler for %s: %v\n", serviceName, err) errors++ continue } generated++ } // Summary fmt.Println("\n📊 Generation Summary:") fmt.Printf("✅ Successfully processed: %d services\n", generated) if errors > 0 { fmt.Printf("❌ Failed: %d services\n", errors) } if generated > 0 { fmt.Println("🎉 Generation completed successfully!") } } // Helper functions func loadConfig(filename string) (*ServiceConfig, error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, err } var config ServiceConfig err = yaml.Unmarshal(data, &config) if err != nil { return nil, err } // Set defaults if config.Global.ModuleName == "" { config.Global.ModuleName = "api-service" } if config.Global.OutputDir == "" { config.Global.OutputDir = "internal/handlers" } return &config, nil } func getOrDefault(value, defaultValue int) int { if value == 0 { return defaultValue } return value } func printUsage() { fmt.Println("BPJS Dynamic Handler Generator with Function Validation") fmt.Println() fmt.Println("Usage:") fmt.Println(" go run generate-handler.go [service-name]") fmt.Println() fmt.Println("Examples:") fmt.Println(" go run generate-handler.go services-config-bpjs.yaml") fmt.Println(" go run generate-handler.go services-config-bpjs.yaml vclaim") } 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, 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 }