package main import ( "fmt" "os" "path/filepath" "strings" "time" ) // BpjsHandlerData contains template data for BPJS handler generation type BpjsHandlerData struct { Name string NameLower string NameUpper string Category string CategoryPath string ModuleName string HasGet bool HasPost bool HasPut bool HasDelete bool GetEndpoint string PostEndpoint string PutEndpoint string DeleteEndpoint string Timestamp string } func main() { if len(os.Args) < 2 { fmt.Println("Usage: go run generate-bpjs-handler.go [category/]entity [methods]") fmt.Println("Examples:") fmt.Println(" go run generate-bpjs-handler.go vclaim/peserta get post put delete") fmt.Println(" go run generate-bpjs-handler.go eclaim/sep get post") fmt.Println(" go run generate-bpjs-handler.go peserta get") os.Exit(1) } // Parse entity path (could be "entity" or "category/entity") entityPath := os.Args[1] methods := []string{} if len(os.Args) > 2 { methods = os.Args[2:] } else { // Default methods if none specified methods = []string{"get", "post", "put", "delete"} } // Parse category and entity var category, entityName string if strings.Contains(entityPath, "/") { parts := strings.Split(entityPath, "/") if len(parts) != 2 { fmt.Println("❌ Error: Invalid path format. Use 'category/entity' or just 'entity'") os.Exit(1) } category = parts[0] entityName = parts[1] } else { category = "" entityName = entityPath } // Format names entityName = strings.Title(entityName) // PascalCase entity name entityLower := strings.ToLower(entityName) entityUpper := strings.ToUpper(entityName) data := BpjsHandlerData{ Name: entityName, NameLower: entityLower, NameUpper: entityUpper, Category: category, CategoryPath: category, ModuleName: "api-service", Timestamp: time.Now().Format("2006-01-02 15:04:05"), } // Set methods and endpoints based on arguments for _, m := range methods { switch strings.ToLower(m) { case "get": data.HasGet = true data.GetEndpoint = fmt.Sprintf("/%s/{id}", entityUpper) case "post": data.HasPost = true data.PostEndpoint = fmt.Sprintf("/%s/2.0/insert", entityUpper) case "put": data.HasPut = true data.PutEndpoint = fmt.Sprintf("/%s/2.0/update", entityUpper) case "delete": data.HasDelete = true data.DeleteEndpoint = fmt.Sprintf("/%s/2.0/delete", entityUpper) } } // Create directories with dynamic logic (sama seperti generate-handler.go) var handlerDir, modelDir string if category != "" { // Dengan kategori: internal/handlers/category/ handlerDir = filepath.Join("internal", "handlers", category) modelDir = filepath.Join("internal", "models", category) } else { // Tanpa kategori: langsung internal/handlers/ handlerDir = filepath.Join("internal", "handlers") modelDir = filepath.Join("internal", "models") } // Create directories for _, d := range []string{handlerDir, modelDir} { if err := os.MkdirAll(d, 0755); err != nil { panic(err) } } // Generate files generateBpjsHandlerFile(data, handlerDir) generateBpjsModelFile(data, modelDir) updateBpjsRoutesFile(data) fmt.Printf("✅ Successfully generated BPJS handler: %s\n", entityName) if category != "" { fmt.Printf("📁 Category: %s\n", category) } fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go")) } // ================= HANDLER GENERATION ===================== func generateBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { // Build import path based on category (sama seperti generate-handler.go) var modelsImportPath string if data.Category != "" { modelsImportPath = data.ModuleName + "/internal/models/" + data.Category } else { modelsImportPath = data.ModuleName + "/internal/models" } handlerContent := `package handlers import ( "context" "fmt" "net/http" "time" "` + data.ModuleName + `/internal/config" models "` + modelsImportPath + `" services "` + data.ModuleName + `/internal/services/bpjs" "github.com/gin-gonic/gin" ) // ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services type ` + data.Name + `Handler struct { service services.VClaimService } // New` + data.Name + `Handler creates a new ` + data.Name + `Handler func New` + data.Name + `Handler(cfg config.BpjsConfig) *` + data.Name + `Handler { return &` + data.Name + `Handler{ service: services.NewService(cfg), } }` // Add methods based on flags if data.HasPost { handlerContent += generateBpjsCreateMethod(data) } if data.HasPut { handlerContent += generateBpjsUpdateMethod(data) } if data.HasDelete { handlerContent += generateBpjsDeleteMethod(data) } if data.HasGet { handlerContent += generateBpjsGetMethod(data) } writeFileBpjs(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) } func generateBpjsCreateMethod(data BpjsHandlerData) string { // Build route path based on category (dynamic, tidak hardcode bpjs/) var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower } else { routePath = data.NameLower } // Tag untuk swagger var tagName string if data.Category != "" { tagName = data.Category + "-" + data.NameLower } else { tagName = data.NameLower } return ` // Create` + data.Name + ` godoc // @Summary Create a new ` + data.NameUpper + ` // @Description Create a new ` + data.Name + ` in BPJS system // @Tags ` + tagName + ` // @Accept json // @Produce json // @Param request body models.` + data.Name + `PostRequest true "` + data.Name + ` creation request" // @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` created successfully" // @Failure 400 {object} gin.H "Invalid request" // @Failure 500 {object} gin.H "Internal server error" // @Router /api/v1/` + routePath + ` [post] func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { var req models.` + data.Name + `PostRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()}) return } ctx, cancel := context.WithTimeout(c, 30*time.Second) defer cancel() var result map[string]interface{} if err := h.service.Post(ctx, "` + data.PostEndpoint + `", req, &result); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed", "message": err.Error()}) return } c.JSON(http.StatusOK, models.` + data.Name + `Response{ Message: "` + data.Name + ` berhasil dibuat", Data: result, }) }` } func generateBpjsUpdateMethod(data BpjsHandlerData) string { var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower } else { routePath = data.NameLower } var tagName string if data.Category != "" { tagName = data.Category + "-" + data.NameLower } else { tagName = data.NameLower } return ` // Update` + data.Name + ` godoc // @Summary Update an existing ` + data.NameUpper + ` // @Description Update an existing ` + data.Name + ` in BPJS system // @Tags ` + tagName + ` // @Accept json // @Produce json // @Param request body models.` + data.Name + `PutRequest true "` + data.Name + ` update request" // @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` updated successfully" // @Failure 400 {object} gin.H "Invalid request" // @Failure 500 {object} gin.H "Internal server error" // @Router /api/v1/` + routePath + ` [put] func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { var req models.` + data.Name + `PutRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()}) return } ctx, cancel := context.WithTimeout(c, 30*time.Second) defer cancel() var result map[string]interface{} if err := h.service.Put(ctx, "` + data.PutEndpoint + `", req, &result); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed", "message": err.Error()}) return } c.JSON(http.StatusOK, models.` + data.Name + `Response{ Message: "` + data.Name + ` berhasil diperbarui", Data: result, }) }` } func generateBpjsDeleteMethod(data BpjsHandlerData) string { var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower } else { routePath = data.NameLower } var tagName string if data.Category != "" { tagName = data.Category + "-" + data.NameLower } else { tagName = data.NameLower } return ` // Delete` + data.Name + ` godoc // @Summary Delete an existing ` + data.NameUpper + ` // @Description Delete a ` + data.Name + ` by ID // @Tags ` + tagName + ` // @Accept json // @Produce json // @Param id path string true "` + data.Name + ` ID" // @Param user query string true "User" // @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` deleted successfully" // @Failure 400 {object} gin.H "Invalid request" // @Failure 500 {object} gin.H "Internal server error" // @Router /api/v1/` + routePath + `/{id} [delete] func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { id := c.Param("id") user := c.Query("user") if id == "" || user == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "id & user required"}) return } body := models.` + data.Name + `DeleteRequest{} body.T` + data.Name + `.ID = id body.T` + data.Name + `.User = user ctx, cancel := context.WithTimeout(c, 30*time.Second) defer cancel() if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed", "message": err.Error()}) return } c.JSON(http.StatusOK, models.` + data.Name + `Response{ Message: "` + data.Name + ` berhasil dihapus", Data: nil, }) }` } func generateBpjsGetMethod(data BpjsHandlerData) string { var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower } else { routePath = data.NameLower } var tagName string if data.Category != "" { tagName = data.Category + "-" + data.NameLower } else { tagName = data.NameLower } return ` // Get` + data.Name + ` godoc // @Summary Get an existing ` + data.NameUpper + ` // @Description Retrieve a ` + data.Name + ` by ID // @Tags ` + tagName + ` // @Accept json // @Produce json // @Param id path string true "` + data.Name + ` ID" // @Success 200 {object} models.` + data.Name + `Response "Data ` + data.Name + ` retrieved successfully" // @Failure 400 {object} gin.H "Invalid request" // @Failure 500 {object} gin.H "Internal server error" // @Router /api/v1/` + routePath + `/{id} [get] func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "id required"}) return } ctx, cancel := context.WithTimeout(c, 30*time.Second) defer cancel() endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) var result map[string]interface{} if err := h.service.Get(ctx, endpoint, &result); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "fetch failed", "message": err.Error()}) return } c.JSON(http.StatusOK, models.` + data.Name + `Response{ Message: "Data ` + data.Name + ` berhasil diambil", Data: result, }) }` } // ================= MODEL GENERATION ===================== func generateBpjsModelFile(data BpjsHandlerData, modelDir string) { modelContent := `package models // ` + data.Name + ` BPJS Models // Generated at: ` + data.Timestamp + ` // Category: ` + data.Category + ` // Common Response Structure type ` + data.Name + `Response struct { Message string ` + "`json:\"message\"`" + ` Data map[string]interface{} ` + "`json:\"data,omitempty\"`" + ` } type ` + data.Name + `RawResponse struct { MetaData struct { Code string ` + "`json:\"code\"`" + ` Message string ` + "`json:\"message\"`" + ` } ` + "`json:\"metaData\"`" + ` Response interface{} ` + "`json:\"response\"`" + ` }` if data.HasPost { modelContent += ` // ` + data.Name + ` POST Request Structure type ` + data.Name + `PostRequest struct { T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` } type ` + data.Name + `Post struct { // Add your specific fields here based on BPJS API requirements NoKartu string ` + "`json:\"noKartu\" binding:\"required\"`" + ` TglLayanan string ` + "`json:\"tglLayanan\" binding:\"required\"`" + ` JnsPelayanan string ` + "`json:\"jnsPelayanan\" binding:\"required\"`" + ` User string ` + "`json:\"user\" binding:\"required\"`" + ` }` } if data.HasPut { modelContent += ` // ` + data.Name + ` PUT Request Structure type ` + data.Name + `PutRequest struct { T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` } type ` + data.Name + `Put struct { ID string ` + "`json:\"id\" binding:\"required\"`" + ` NoKartu string ` + "`json:\"noKartu\"`" + ` TglLayanan string ` + "`json:\"tglLayanan\"`" + ` JnsPelayanan string ` + "`json:\"jnsPelayanan\"`" + ` User string ` + "`json:\"user\" binding:\"required\"`" + ` }` } if data.HasDelete { modelContent += ` // ` + data.Name + ` DELETE Request Structure type ` + data.Name + `DeleteRequest struct { T` + data.Name + ` struct { ID string ` + "`json:\"id\" binding:\"required\"`" + ` User string ` + "`json:\"user\" binding:\"required\"`" + ` } ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` }` } // Add helper structures modelContent += ` // Common Helper Structures type Flag struct { Flag string ` + "`json:\"flag\" binding:\"required\"`" + ` } type Poli struct { Tujuan string ` + "`json:\"tujuan\"`" + ` Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\"`" + ` } // Validation helpers func IsValidStatus(status string) bool { validStatuses := []string{"active", "inactive", "pending", "processed"} for _, v := range validStatuses { if v == status { return true } } return false }` writeFileBpjs(filepath.Join(modelDir, data.NameLower+".go"), modelContent) } // ================= ROUTES GENERATION ===================== func updateBpjsRoutesFile(data BpjsHandlerData) { routesFile := "internal/routes/v1/routes.go" content, err := os.ReadFile(routesFile) if err != nil { fmt.Printf("⚠️ Could not read routes.go: %v\n", err) fmt.Printf("📝 Please manually add these routes to your routes.go file:\n") printBpjsRoutesSample(data) return } routesContent := string(content) // Build import path berdasarkan category (sama seperti generate-handler.go) var importPath, importAlias string if data.Category != "" { importPath = fmt.Sprintf("%s/internal/handlers/%s", data.ModuleName, data.Category) importAlias = data.NameLower + "Handlers" } else { importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) importAlias = data.NameLower + "Handlers" } // Check and add import importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath) if !strings.Contains(routesContent, importPattern) { importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath) if strings.Contains(routesContent, "import (") { routesContent = strings.Replace(routesContent, "import (", "import (\n"+importToAdd, 1) } } // Build routes newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name) if data.Category != "" { newRoutes = fmt.Sprintf("\t\t// %s %s endpoints\n", data.Category, data.Name) } newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler(config.LoadConfig().Bpjs)\n", data.NameLower, importAlias, data.Name) // Build route paths berdasarkan category (dynamic, tidak hardcode) var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower } else { routePath = data.NameLower } if data.HasGet { newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", routePath, data.NameLower, data.Name) } if data.HasPost { newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routePath, data.NameLower, data.Name) } if data.HasPut { newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", routePath, data.NameLower, data.Name) } if data.HasDelete { newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", routePath, data.NameLower, data.Name) } newRoutes += "\n" // Insert routes insertMarker := "\t\tprotected := v1.Group(\"/\")" if strings.Contains(routesContent, insertMarker) { if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { routesContent = strings.Replace(routesContent, insertMarker, newRoutes+insertMarker, 1) } else { fmt.Printf("✅ Routes for %s already exist, skipping...\n", data.Name) return } } if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { fmt.Printf("Error writing routes.go: %v\n", err) return } if data.Category != "" { fmt.Printf("✅ Updated routes.go with %s %s endpoints\n", data.Category, data.Name) } else { fmt.Printf("✅ Updated routes.go with %s endpoints\n", data.Name) } } func printBpjsRoutesSample(data BpjsHandlerData) { var importAlias string if data.Category != "" { importAlias = data.NameLower + "Handlers" } else { importAlias = data.NameLower + "Handlers" } var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower } else { routePath = data.NameLower } if data.Category != "" { fmt.Printf(` // %s %s endpoints %sHandler := %s.New%sHandler(config.LoadConfig().Bpjs) `, data.Category, data.Name, data.NameLower, importAlias, data.Name) } else { fmt.Printf(` // %s endpoints %sHandler := %s.New%sHandler(config.LoadConfig().Bpjs) `, data.Name, data.NameLower, importAlias, data.Name) } if data.HasGet { fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", routePath, data.NameLower, data.Name) } if data.HasPost { fmt.Printf("\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routePath, data.NameLower, data.Name) } if data.HasPut { fmt.Printf("\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", routePath, data.NameLower, data.Name) } if data.HasDelete { fmt.Printf("\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", routePath, data.NameLower, data.Name) } fmt.Println() } // ================= UTILITY FUNCTIONS ===================== func writeFileBpjs(filename, content string) { if err := os.WriteFile(filename, []byte(content), 0644); err != nil { fmt.Printf("❌ Error creating file %s: %v\n", filename, err) return } fmt.Printf("✅ Generated: %s\n", filename) }