diff --git a/internal/services/bpjs/vclaimAdapter.go b/adapter similarity index 100% rename from internal/services/bpjs/vclaimAdapter.go rename to adapter diff --git a/generetebpjs b/generetebpjs new file mode 100644 index 0000000..ada9d50 --- /dev/null +++ b/generetebpjs @@ -0,0 +1,609 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + "gopkg.in/yaml.v2" +) + +// ServiceConfig represents the main configuration structure +type ServiceConfig struct { + Services map[string]Service `yaml:"services"` + Global GlobalConfig `yaml:"global,omitempty"` +} + +// GlobalConfig contains global configuration +type GlobalConfig struct { + ModuleName string `yaml:"module_name"` + OutputDir string `yaml:"output_dir"` + PackagePrefix string `yaml:"package_prefix"` + EnableSwagger bool `yaml:"enable_swagger"` + EnableLogging bool `yaml:"enable_logging"` + EnableMetrics bool `yaml:"enable_metrics"` +} + +// Service represents individual service configuration +type Service struct { + Name string `yaml:"name"` + Category string `yaml:"category"` + Package string `yaml:"package"` + Description string `yaml:"description"` + BaseURL string `yaml:"base_url"` + Timeout int `yaml:"timeout"` + RetryCount int `yaml:"retry_count"` + Endpoints map[string]Endpoint `yaml:"endpoints"` + Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string + Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string +} + +// Endpoint represents endpoint configuration +type Endpoint struct { + Methods []string `yaml:"methods"` + GetPath string `yaml:"get_path,omitempty"` + PostPath string `yaml:"post_path,omitempty"` + PutPath string `yaml:"put_path,omitempty"` + DeletePath string `yaml:"delete_path,omitempty"` + PatchPath string `yaml:"patch_path,omitempty"` + Model string `yaml:"model"` + ResponseModel string `yaml:"response_model"` + Description string `yaml:"description"` + Summary string `yaml:"summary"` + Tags []string `yaml:"tags"` + RequireAuth bool `yaml:"require_auth"` + RateLimit int `yaml:"rate_limit,omitempty"` + CacheEnabled bool `yaml:"cache_enabled"` + CacheTTL int `yaml:"cache_ttl,omitempty"` + CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` // ADDED: Missing field +} + +// TemplateData holds data for generating handlers +type TemplateData struct { + ServiceName string + ServiceLower string + ServiceUpper string + Category string + Package string + Description string + BaseURL string + Timeout int + RetryCount int + Endpoints []EndpointData + Timestamp string + ModuleName string + HasValidator bool + HasLogger bool + HasMetrics bool + HasSwagger bool + HasAuth bool + HasCache bool + Dependencies []string // FIXED: Changed to []string + Middleware []string // FIXED: Changed to []string + GlobalConfig GlobalConfig +} + +// EndpointData represents processed endpoint data +type EndpointData struct { + Name string + NameLower string + NameUpper string + NameCamel string + Methods []string + GetPath string + PostPath string + PutPath string + DeletePath string + PatchPath string + Model string + ResponseModel string + Description string + Summary string + Tags []string + HasGet bool + HasPost bool + HasPut bool + HasDelete bool + HasPatch bool + RequireAuth bool + RateLimit int + CacheEnabled bool + CacheTTL int + PathParams []string + QueryParams []string + RequiredFields []string + OptionalFields []string + CustomHeaders map[string]string +} + +// Template remains the same as before... +const handlerTemplate = ` +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: {{.Timestamp}} +// Service: {{.ServiceName}} ({{.Category}}) +// Description: {{.Description}} + +package handlers + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/models/reference" + "{{.ModuleName}}/internal/services/bpjs" + "{{.ModuleName}}/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services +type {{.ServiceName}}Handler struct { + service services.{{.ServiceName}}Service + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler +type {{.ServiceName}}HandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler +func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { + return &{{.ServiceName}}Handler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +{{range .Endpoints}} +{{if .HasGet}} +// Get{{.NameUpper}} retrieves {{.Name}} data +{{if $.HasSwagger}} +// @Summary Get {{.Name}} data +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +{{range .PathParams}} +// @Param {{.}} path string true "{{.}}" +{{end}} +// @Success 200 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.GetPath}} [get] +{{end}} +func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(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, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter {{.}}", + RequestID: requestID, + }) + return + } + {{end}} + + // Call service method + var response reference.{{.ResponseModel}} + {{if .PathParams}} + result, err := h.service.Get{{.NameUpper}}(ctx{{range .PathParams}}, {{.}}{{end}}) + {{else}} + result, err := h.service.Get{{.NameUpper}}(ctx) + {{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, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} +{{end}} + +{{if .HasPost}} +// Create{{.NameUpper}} creates new {{.Name}} +{{if $.HasSwagger}} +// @Summary Create {{.Name}} +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +// @Param request body reference.{{.Model}} true "{{.Name}} data" +// @Success 201 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.PostPath}} [post] +{{end}} +func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + + var req reference.{{.Model}} + if err := c.ShouldBindJSON(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.{{.ResponseModel}} + result, err := h.service.Create{{.NameUpper}}(ctx, &req) + if err != nil { + {{if $.HasLogger}} + h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusCreated, response) +} +{{end}} +{{end}} +` + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(1) + } + + configFile := os.Args[1] + var targetService string + if len(os.Args) > 2 { + targetService = os.Args[2] + } + + // Load configuration + config, err := loadConfig(configFile) + if err != nil { + fmt.Printf("āŒ Error loading config: %v\n", err) + os.Exit(1) + } + + fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation...") + fmt.Printf("šŸ“ Config file: %s\n", configFile) + if targetService != "" { + fmt.Printf("šŸŽÆ Target service: %s\n", targetService) + } + + generated := 0 + errors := 0 + + // Generate handlers + for serviceName, service := range config.Services { + if targetService != "" && serviceName != targetService { + continue + } + + fmt.Printf("šŸ”§ Generating handler for service: %s (%s)\n", service.Name, service.Category) + err := generateHandler(serviceName, service, config.Global) + if err != nil { + fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) + errors++ + continue + } + fmt.Printf("āœ… Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) + generated++ + } + + // Summary + fmt.Println("\nšŸ“Š Generation Summary:") + fmt.Printf(" āœ… Successfully generated: %d handlers\n", generated) + if errors > 0 { + fmt.Printf(" āŒ Failed: %d handlers\n", errors) + } + + if generated > 0 { + fmt.Println("šŸŽ‰ Generation completed successfully!") + } +} + +func printUsage() { + fmt.Println("BPJS Dynamic Handler Generator") + fmt.Println() + fmt.Println("Usage:") + fmt.Println(" go run generate-dynamic-handler.go [service-name]") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") + fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") +} + +func loadConfig(filename string) (*ServiceConfig, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + var config ServiceConfig + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("failed to parse YAML config: %w", err) + } + + // Set default values + if config.Global.ModuleName == "" { + config.Global.ModuleName = "api-service" + } + if config.Global.OutputDir == "" { + config.Global.OutputDir = "internal/handlers" + } + + return &config, nil +} + +func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { + // Prepare template data + templateData := TemplateData{ + ServiceName: service.Name, + ServiceLower: strings.ToLower(service.Name), + ServiceUpper: strings.ToUpper(service.Name), + Category: service.Category, + Package: service.Package, + 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, // Now []string + Middleware: service.Middleware, // Now []string + GlobalConfig: globalConfig, + } + + // Check for advanced features + for _, endpoint := range service.Endpoints { + if endpoint.RequireAuth { + templateData.HasAuth = true + } + if endpoint.CacheEnabled { + templateData.HasCache = true + } + } + + // Process endpoints + for endpointName, endpoint := range service.Endpoints { + endpointData := processEndpoint(endpointName, endpoint) + templateData.Endpoints = append(templateData.Endpoints, endpointData) + } + + // Create output directory + outputDir := globalConfig.OutputDir + if outputDir == "" { + outputDir = "internal/handlers" + } + if err := os.MkdirAll(outputDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // Generate handler file + filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) + + // Create template with custom functions + tmpl := template.New("handler").Funcs(template.FuncMap{ + "contains": strings.Contains, + "join": strings.Join, + "title": strings.Title, + "trimPrefix": strings.TrimPrefix, + }) + + tmpl, err := tmpl.Parse(handlerTemplate) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + err = tmpl.Execute(file, templateData) + if err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + return nil +} + +func processEndpoint(name string, endpoint Endpoint) 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, + } + + // 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 +} diff --git a/genet b/genet new file mode 100644 index 0000000..f6a57ed --- /dev/null +++ b/genet @@ -0,0 +1,625 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + "gopkg.in/yaml.v2" +) + +// ServiceConfig represents the main configuration structure +type ServiceConfig struct { + Services map[string]Service `yaml:"services"` + Global GlobalConfig `yaml:"global,omitempty"` +} + +// GlobalConfig contains global configuration +type GlobalConfig struct { + ModuleName string `yaml:"module_name"` + OutputDir string `yaml:"output_dir"` + PackagePrefix string `yaml:"package_prefix"` + EnableSwagger bool `yaml:"enable_swagger"` + EnableLogging bool `yaml:"enable_logging"` + EnableMetrics bool `yaml:"enable_metrics"` +} + +// Service represents individual service configuration +type Service struct { + Name string `yaml:"name"` + Category string `yaml:"category"` + Package string `yaml:"package"` + Description string `yaml:"description"` + BaseURL string `yaml:"base_url"` + Timeout int `yaml:"timeout"` + RetryCount int `yaml:"retry_count"` + Endpoints map[string]Endpoint `yaml:"endpoints"` + Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string + Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string +} + +// Endpoint represents endpoint configuration +type Endpoint struct { + Methods []string `yaml:"methods"` + GetPath string `yaml:"get_path,omitempty"` + PostPath string `yaml:"post_path,omitempty"` + PutPath string `yaml:"put_path,omitempty"` + DeletePath string `yaml:"delete_path,omitempty"` + PatchPath string `yaml:"patch_path,omitempty"` + Model string `yaml:"model"` + ResponseModel string `yaml:"response_model"` + Description string `yaml:"description"` + Summary string `yaml:"summary"` + Tags []string `yaml:"tags"` + RequireAuth bool `yaml:"require_auth"` + RateLimit int `yaml:"rate_limit,omitempty"` + CacheEnabled bool `yaml:"cache_enabled"` + CacheTTL int `yaml:"cache_ttl,omitempty"` + CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` // ADDED: Missing field +} + +// TemplateData holds data for generating handlers +type TemplateData struct { + ServiceName string + ServiceLower string + ServiceUpper string + Category string + Package string + Description string + BaseURL string + Timeout int + RetryCount int + Endpoints []EndpointData + Timestamp string + ModuleName string + HasValidator bool + HasLogger bool + HasMetrics bool + HasSwagger bool + HasAuth bool + HasCache bool + Dependencies []string // FIXED: Changed to []string + Middleware []string // FIXED: Changed to []string + GlobalConfig GlobalConfig +} + +// EndpointData represents processed endpoint data +type EndpointData struct { + Name string + NameLower string + NameUpper string + NameCamel string + Methods []string + GetPath string + PostPath string + PutPath string + DeletePath string + PatchPath string + Model string + ResponseModel string + Description string + Summary string + Tags []string + HasGet bool + HasPost bool + HasPut bool + HasDelete bool + HasPatch bool + RequireAuth bool + RateLimit int + CacheEnabled bool + CacheTTL int + PathParams []string + QueryParams []string + RequiredFields []string + OptionalFields []string + CustomHeaders map[string]string +} + +// Template remains the same as before... +const handlerTemplate = ` +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: {{.Timestamp}} +// Service: {{.ServiceName}} ({{.Category}}) +// Description: {{.Description}} + +package handlers + +import ( + "context" + "net/http" + "time" + + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/models/reference" + "{{.ModuleName}}/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// {{.ServiceName}}Service defines {{.ServiceName}} service interface +type {{.ServiceName}}Service interface { + {{range .Endpoints}} + {{if .HasGet}} + Get{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) (*reference.{{.Name}}Data, error) + {{end}} + {{if .HasPost}} + Create{{.Name}}(ctx context.Context, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) + {{end}} + {{if .HasPut}} + Update{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) + {{end}} + {{if .HasDelete}} + Delete{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) error + {{end}} + {{end}} +} + +// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services +type {{.ServiceName}}Handler struct { + service {{.ServiceName}}Service + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler +type {{.ServiceName}}HandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler +func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig, service {{.ServiceName}}Service) *{{.ServiceName}}Handler { + return &{{.ServiceName}}Handler{ + service: service, + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +{{range .Endpoints}} +{{if .HasGet}} +// Get{{.NameUpper}} retrieves {{.Name}} data +{{if $.HasSwagger}} +// @Summary Get {{.Name}} data +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +{{range .PathParams}} +// @Param {{.}} path string true "{{.}}" +{{end}} +// @Success 200 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.GetPath}} [get] +{{end}} +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, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter {{.}}", + RequestID: requestID, + }) + return + } + {{end}} + + // Call service method + var response reference.{{.ResponseModel}} + {{if .PathParams}} + result, err := h.service.Get{{.Name}}(ctx{{range .PathParams}}, {{.}}{{end}}) + {{else}} + result, err := h.service.Get{{.Name}}(ctx) + {{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, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusOK, response) +} +{{end}} + +{{if .HasPost}} +// Create{{.NameUpper}} creates new {{.Name}} +{{if $.HasSwagger}} +// @Summary Create {{.Name}} +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +// @Param request body reference.{{.Model}} true "{{.Name}} data" +// @Success 201 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.PostPath}} [post] +{{end}} +func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + + var req reference.{{.Model}} + if err := c.ShouldBindJSON(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.{{.ResponseModel}} + result, err := h.service.Create{{.Name}}(ctx, &req) + if err != nil { + {{if $.HasLogger}} + h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusCreated, response) +} +{{end}} +{{end}} +` + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(1) + } + + configFile := os.Args[1] + var targetService string + if len(os.Args) > 2 { + targetService = os.Args[2] + } + + // Load configuration + config, err := loadConfig(configFile) + if err != nil { + fmt.Printf("āŒ Error loading config: %v\n", err) + os.Exit(1) + } + + fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation...") + fmt.Printf("šŸ“ Config file: %s\n", configFile) + if targetService != "" { + fmt.Printf("šŸŽÆ Target service: %s\n", targetService) + } + + generated := 0 + errors := 0 + + // Generate handlers + for serviceName, service := range config.Services { + if targetService != "" && serviceName != targetService { + continue + } + + fmt.Printf("šŸ”§ Generating handler for service: %s (%s)\n", service.Name, service.Category) + err := generateHandler(serviceName, service, config.Global) + if err != nil { + fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) + errors++ + continue + } + fmt.Printf("āœ… Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) + generated++ + } + + // Summary + fmt.Println("\nšŸ“Š Generation Summary:") + fmt.Printf(" āœ… Successfully generated: %d handlers\n", generated) + if errors > 0 { + fmt.Printf(" āŒ Failed: %d handlers\n", errors) + } + + if generated > 0 { + fmt.Println("šŸŽ‰ Generation completed successfully!") + } +} + +func printUsage() { + fmt.Println("BPJS Dynamic Handler Generator") + fmt.Println() + fmt.Println("Usage:") + fmt.Println(" go run generate-dynamic-handler.go [service-name]") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") + fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") +} + +func loadConfig(filename string) (*ServiceConfig, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + var config ServiceConfig + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("failed to parse YAML config: %w", err) + } + + // Set default values + if config.Global.ModuleName == "" { + config.Global.ModuleName = "api-service" + } + if config.Global.OutputDir == "" { + config.Global.OutputDir = "internal/handlers" + } + + return &config, nil +} + +func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { + // Prepare template data + templateData := TemplateData{ + ServiceName: service.Name, + ServiceLower: strings.ToLower(service.Name), + ServiceUpper: strings.ToUpper(service.Name), + Category: service.Category, + Package: service.Package, + 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, // Now []string + Middleware: service.Middleware, // Now []string + GlobalConfig: globalConfig, + } + + // Check for advanced features + for _, endpoint := range service.Endpoints { + if endpoint.RequireAuth { + templateData.HasAuth = true + } + if endpoint.CacheEnabled { + templateData.HasCache = true + } + } + + // Process endpoints + for endpointName, endpoint := range service.Endpoints { + endpointData := processEndpoint(endpointName, endpoint) + templateData.Endpoints = append(templateData.Endpoints, endpointData) + } + + // Create output directory + outputDir := globalConfig.OutputDir + if outputDir == "" { + outputDir = "internal/handlers" + } + if err := os.MkdirAll(outputDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // Generate handler file + filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) + + // Create template with custom functions + tmpl := template.New("handler").Funcs(template.FuncMap{ + "contains": strings.Contains, + "join": strings.Join, + "title": strings.Title, + "trimPrefix": strings.TrimPrefix, + }) + + tmpl, err := tmpl.Parse(handlerTemplate) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + err = tmpl.Execute(file, templateData) + if err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + return nil +} + +func processEndpoint(name string, endpoint Endpoint) 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, + } + + // 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 +} diff --git a/internal/handlers/vclaim_handler.go b/internal/handlers/vclaim_handler.go index 62a154f..0322226 100644 --- a/internal/handlers/vclaim_handler.go +++ b/internal/handlers/vclaim_handler.go @@ -1,5 +1,5 @@ // Code generated by generate-dynamic-handler.go; DO NOT EDIT. -// Generated at: 2025-08-29 13:02:57 +// Generated at: 2025-09-01 11:57:39 // Service: VClaim (vclaim) // Description: BPJS VClaim service for eligibility and SEP management @@ -12,7 +12,6 @@ import ( "api-service/internal/config" "api-service/internal/models/reference" - services "api-service/internal/services/bpjs" "api-service/pkg/logger" "github.com/gin-gonic/gin" @@ -20,9 +19,22 @@ import ( "github.com/google/uuid" ) +// VClaimService defines VClaim service interface +type VClaimService interface { + GetPeserta(ctx context.Context, nokartu string) (*reference.PesertaData, error) + + GetSep(ctx context.Context, nosep string) (*reference.SepData, error) + + CreateSep(ctx context.Context, req *reference.SepRequest) (*reference.SepData, error) + + UpdateSep(ctx context.Context, nosep string, req *reference.SepRequest) (*reference.SepData, error) + + DeleteSep(ctx context.Context, nosep string) error +} + // VClaimHandler handles VClaim BPJS services type VClaimHandler struct { - service reference.VClaimService + service VClaimService validator *validator.Validate logger logger.Logger config config.BpjsConfig @@ -36,11 +48,9 @@ type VClaimHandlerConfig struct { } // NewVClaimHandler creates a new VClaimHandler -func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { - baseService := services.NewService(cfg.BpjsConfig) - adapter := services.NewVClaimAdapter(baseService) +func NewVClaimHandler(cfg VClaimHandlerConfig, service VClaimService) *VClaimHandler { return &VClaimHandler{ - service: adapter, + service: service, validator: cfg.Validator, logger: cfg.Logger, config: cfg.BpjsConfig, @@ -62,7 +72,7 @@ func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { // @Failure 500 {object} reference.ErrorResponse // @Router /peserta/:nokartu [get] -func (h *VClaimHandler) GetPESERTA(c *gin.Context) { +func (h *VClaimHandler) GetPeserta(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel() @@ -101,7 +111,9 @@ func (h *VClaimHandler) GetPESERTA(c *gin.Context) { var response reference.PesertaResponse result, err := h.service.GetPeserta(ctx, nokartu) + if err != nil { + h.logger.Error("Failed to get Peserta", map[string]interface{}{ "error": err.Error(), "request_id": requestID, @@ -115,7 +127,7 @@ func (h *VClaimHandler) GetPESERTA(c *gin.Context) { return } - // Assign result to response + // Ensure response has proper fields response.Status = "success" response.RequestID = requestID response.Data = result @@ -132,12 +144,12 @@ func (h *VClaimHandler) GetPESERTA(c *gin.Context) { // @Param nosep path string true "nosep" -// @Success 200 {object} reference.SEPResponse +// @Success 200 {object} reference.SepResponse // @Failure 400 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse // @Router /sep/:nosep [get] -func (h *VClaimHandler) GetSEP(c *gin.Context) { +func (h *VClaimHandler) GetSep(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel() @@ -173,10 +185,12 @@ func (h *VClaimHandler) GetSEP(c *gin.Context) { } // Call service method - var response reference.SEPResponse + var response reference.SepResponse + + result, err := h.service.GetSep(ctx, nosep) - result, err := h.service.GetSEP(ctx, nosep) if err != nil { + h.logger.Error("Failed to get Sep", map[string]interface{}{ "error": err.Error(), "request_id": requestID, @@ -190,7 +204,7 @@ func (h *VClaimHandler) GetSEP(c *gin.Context) { return } - // Assign result to response + // Ensure response has proper fields response.Status = "success" response.RequestID = requestID response.Data = result @@ -205,13 +219,13 @@ func (h *VClaimHandler) GetSEP(c *gin.Context) { // @Accept json // @Produce json -// @Param request body reference.SEPRequest true "Sep data" -// @Success 201 {object} reference.SEPResponse +// @Param request body reference.SepRequest true "Sep data" +// @Success 201 {object} reference.SepResponse // @Failure 400 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse // @Router /sep [post] -func (h *VClaimHandler) CreateSEP(c *gin.Context) { +func (h *VClaimHandler) CreateSep(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel() @@ -225,7 +239,7 @@ func (h *VClaimHandler) CreateSEP(c *gin.Context) { "request_id": requestID, }) - var req reference.SEPRequest + var req reference.SepRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Error("Invalid request body", map[string]interface{}{ @@ -258,9 +272,10 @@ func (h *VClaimHandler) CreateSEP(c *gin.Context) { } // Call service method - var response reference.SEPResponse - result, err := h.service.CreateSEP(ctx, &req) + var response reference.SepResponse + result, err := h.service.CreateSep(ctx, &req) if err != nil { + h.logger.Error("Failed to create Sep", map[string]interface{}{ "error": err.Error(), "request_id": requestID, @@ -274,9 +289,179 @@ func (h *VClaimHandler) CreateSEP(c *gin.Context) { return } - // Assign result to response + // Ensure response has proper fields response.Status = "success" response.RequestID = requestID response.Data = result c.JSON(http.StatusCreated, response) } + +// UpdateSEP updates existing Sep + +// @Summary Update Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json + +// @Param nosep path string true "nosep" + +// @Param request body reference.SepRequest true "Sep data" +// @Success 200 {object} reference.SepResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep/:nosep [put] + +func (h *VClaimHandler) UpdateSep(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing UpdateSep request", map[string]interface{}{ + "request_id": requestID, + }) + + nosep := c.Param("nosep") + if nosep == "" { + + h.logger.Error("Missing required parameter nosep", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nosep", + RequestID: requestID, + }) + return + } + + var req reference.SepRequest + if err := c.ShouldBindJSON(&req); err != nil { + + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.SepResponse + + result, err := h.service.UpdateSep(ctx, nosep, &req) + + if err != nil { + + h.logger.Error("Failed to update Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusOK, response) +} + +// DeleteSEP deletes existing Sep + +// @Summary Delete Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json + +// @Param nosep path string true "nosep" + +// @Success 204 {object} nil +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep/:nosep [delete] + +func (h *VClaimHandler) DeleteSep(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing DeleteSep request", map[string]interface{}{ + "request_id": requestID, + }) + + nosep := c.Param("nosep") + if nosep == "" { + + h.logger.Error("Missing required parameter nosep", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nosep", + RequestID: requestID, + }) + return + } + + // Call service method + + err := h.service.DeleteSep(ctx, nosep) + + if err != nil { + + h.logger.Error("Failed to delete Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + c.Status(http.StatusNoContent) +} diff --git a/internal/models/reference/services.go b/internal/models/reference/services.go index abba955..fff8eea 100644 --- a/internal/models/reference/services.go +++ b/internal/models/reference/services.go @@ -1,29 +1,27 @@ // internal/models/reference/services.go package reference -import "context" - // VClaimService defines VClaim service interface -type VClaimService interface { - GetPeserta(ctx context.Context, noKartu string) (*PesertaData, error) - CreateSEP(ctx context.Context, req *SEPRequest) (*SEPData, error) - GetSEP(ctx context.Context, noSep string) (*SEPData, error) - UpdateSEP(ctx context.Context, noSep string, req *SEPRequest) (*SEPData, error) - DeleteSEP(ctx context.Context, noSep string) error - GetRujukan(ctx context.Context, noRujukan string) (*RujukanData, error) -} +// type VClaimService interface { +// GetPESERTA(ctx context.Context, nokartu string) (*PesertaData, error) +// CreateSEP(ctx context.Context, req *SEPRequest) (*SEPData, error) +// GetSEP(ctx context.Context, nosep string) (*SEPData, error) +// UpdateSEP(ctx context.Context, nosep string, req *SEPRequest) (*SEPData, error) +// DeleteSEP(ctx context.Context, nosep string) error +// GetRujukan(ctx context.Context, norujukan string) (*RujukanData, error) +// } -// EClaimService defines EClaim service interface -type EClaimService interface { - CreateKlaim(ctx context.Context, req *KlaimRequest) (*KlaimResponseData, error) - GetKlaim(ctx context.Context, noKlaim string) (*KlaimResponseData, error) - UpdateKlaim(ctx context.Context, noKlaim string, req *KlaimRequest) (*KlaimResponseData, error) - ProcessGrouper(ctx context.Context, req *GrouperRequest) (*GrouperResult, error) -} +// // EClaimService defines EClaim service interface +// type EClaimService interface { +// CreateKlaim(ctx context.Context, req *KlaimRequest) (*KlaimResponseData, error) +// GetKlaim(ctx context.Context, noKlaim string) (*KlaimResponseData, error) +// UpdateKlaim(ctx context.Context, noKlaim string, req *KlaimRequest) (*KlaimResponseData, error) +// ProcessGrouper(ctx context.Context, req *GrouperRequest) (*GrouperResult, error) +// } -// AplicareService defines Aplicare service interface -type AplicareService interface { - GetReferensi(ctx context.Context, req *ReferensiRequest) ([]ReferensiData, *PaginationResponse, error) - GetMonitoring(ctx context.Context, req *MonitoringRequest) ([]MonitoringData, *MonitoringSummary, *PaginationResponse, error) - CreateMonitoring(ctx context.Context, req *MonitoringRequest) error -} +// // AplicareService defines Aplicare service interface +// type AplicareService interface { +// GetReferensi(ctx context.Context, req *ReferensiRequest) ([]ReferensiData, *PaginationResponse, error) +// GetMonitoring(ctx context.Context, req *MonitoringRequest) ([]MonitoringData, *MonitoringSummary, *PaginationResponse, error) +// CreateMonitoring(ctx context.Context, req *MonitoringRequest) error +// } diff --git a/internal/models/reference/vclaim.go b/internal/models/reference/vclaim.go index e2378a5..6f3663f 100644 --- a/internal/models/reference/vclaim.go +++ b/internal/models/reference/vclaim.go @@ -54,7 +54,7 @@ type PesertaResponse struct { // === SEP (Surat Eligibilitas Peserta) MODELS === // SEPRequest represents SEP creation/update request -type SEPRequest struct { +type SepRequest struct { BaseRequest NoKartu string `json:"noKartu" validate:"required"` TglSep string `json:"tglSep" validate:"required"` @@ -62,7 +62,7 @@ type SEPRequest struct { JnsPelayanan string `json:"jnsPelayanan" validate:"required,oneof=1 2"` KlsRawat string `json:"klsRawat" validate:"required,oneof=1 2 3"` NoMR string `json:"noMR" validate:"required"` - Rujukan *SEPRujukan `json:"rujukan"` + Rujukan *SepRujukan `json:"rujukan"` Catatan string `json:"catatan,omitempty"` Diagnosa string `json:"diagnosa" validate:"required"` PoliTujuan string `json:"poli" validate:"required"` @@ -71,7 +71,7 @@ type SEPRequest struct { } // SEPRujukan represents rujukan information in SEP -type SEPRujukan struct { +type SepRujukan struct { AsalRujukan string `json:"asalRujukan" validate:"required,oneof=1 2"` TglRujukan string `json:"tglRujukan" validate:"required"` NoRujukan string `json:"noRujukan" validate:"required"` @@ -79,14 +79,14 @@ type SEPRujukan struct { } // SEPData represents SEP response data -type SEPData struct { +type SepData struct { NoSep string `json:"noSep"` TglSep string `json:"tglSep"` JnsPelayanan string `json:"jnsPelayanan"` PoliTujuan string `json:"poli"` KlsRawat string `json:"klsRawat"` NoMR string `json:"noMR"` - Rujukan SEPRujukan `json:"rujukan"` + Rujukan SepRujukan `json:"rujukan"` Catatan string `json:"catatan"` Diagnosa string `json:"diagnosa"` Peserta PesertaData `json:"peserta"` @@ -99,9 +99,9 @@ type SEPData struct { } // SEPResponse represents SEP API response -type SEPResponse struct { +type SepResponse struct { BaseResponse - Data *SEPData `json:"data,omitempty"` + Data *SepData `json:"data,omitempty"` } // === RUJUKAN MODELS === diff --git a/services-config-bpjs.yaml b/services-config-bpjs.yaml index 82b9b09..e6809f6 100644 --- a/services-config-bpjs.yaml +++ b/services-config-bpjs.yaml @@ -31,8 +31,8 @@ services: post_path: "/sep" put_path: "/sep/:nosep" delete_path: "/sep/:nosep" - model: "SEPRequest" - response_model: "SEPResponse" + model: "SepRequest" + response_model: "SepResponse" description: "Manage SEP (Surat Eligibilitas Peserta)" summary: "SEP Management" tags: ["vclaim", "sep"] diff --git a/tools/bpjs/generate-handler.go b/tools/bpjs/generate-handler.go index ada9d50..93b5b25 100644 --- a/tools/bpjs/generate-handler.go +++ b/tools/bpjs/generate-handler.go @@ -131,15 +131,11 @@ package handlers import ( "context" - "fmt" "net/http" - "strconv" - "strings" "time" "{{.ModuleName}}/internal/config" "{{.ModuleName}}/internal/models/reference" - "{{.ModuleName}}/internal/services/bpjs" "{{.ModuleName}}/pkg/logger" "github.com/gin-gonic/gin" @@ -147,9 +143,27 @@ import ( "github.com/google/uuid" ) +// {{.ServiceName}}Service defines {{.ServiceName}} service interface +type {{.ServiceName}}Service interface { + {{range .Endpoints}} + {{if .HasGet}} + Get{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) (*reference.{{.Name}}Data, error) + {{end}} + {{if .HasPost}} + Create{{.Name}}(ctx context.Context, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) + {{end}} + {{if .HasPut}} + Update{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) + {{end}} + {{if .HasDelete}} + Delete{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) error + {{end}} + {{end}} +} + // {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services type {{.ServiceName}}Handler struct { - service services.{{.ServiceName}}Service + service {{.ServiceName}}Service validator *validator.Validate logger logger.Logger config config.BpjsConfig @@ -163,9 +177,9 @@ type {{.ServiceName}}HandlerConfig struct { } // New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler -func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { +func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig, service {{.ServiceName}}Service) *{{.ServiceName}}Handler { return &{{.ServiceName}}Handler{ - service: services.NewService(cfg.BpjsConfig), + service: service, validator: cfg.Validator, logger: cfg.Logger, config: cfg.BpjsConfig, @@ -192,7 +206,7 @@ func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceNa // @Failure 500 {object} reference.ErrorResponse // @Router {{.GetPath}} [get] {{end}} -func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) { +func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() @@ -234,9 +248,9 @@ func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) { // Call service method var response reference.{{.ResponseModel}} {{if .PathParams}} - result, err := h.service.Get{{.NameUpper}}(ctx{{range .PathParams}}, {{.}}{{end}}) + result, err := h.service.Get{{.Name}}(ctx{{range .PathParams}}, {{.}}{{end}}) {{else}} - result, err := h.service.Get{{.NameUpper}}(ctx) + result, err := h.service.Get{{.Name}}(ctx) {{end}} if err != nil { {{if $.HasLogger}} @@ -256,6 +270,7 @@ func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) { // Ensure response has proper fields response.Status = "success" response.RequestID = requestID + response.Data = result c.JSON(http.StatusOK, response) } {{end}} @@ -277,7 +292,7 @@ func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) { // @Failure 500 {object} reference.ErrorResponse // @Router {{.PostPath}} [post] {{end}} -func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) { +func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() @@ -327,7 +342,7 @@ func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) { // Call service method var response reference.{{.ResponseModel}} - result, err := h.service.Create{{.NameUpper}}(ctx, &req) + result, err := h.service.Create{{.Name}}(ctx, &req) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ @@ -346,9 +361,202 @@ func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) { // Ensure response has proper fields response.Status = "success" response.RequestID = requestID + response.Data = result c.JSON(http.StatusCreated, response) } {{end}} + +{{if .HasPut}} +// Update{{.NameUpper}} updates existing {{.Name}} +{{if $.HasSwagger}} +// @Summary Update {{.Name}} +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +{{range .PathParams}} +// @Param {{.}} path string true "{{.}}" +{{end}} +// @Param request body reference.{{.Model}} true "{{.Name}} data" +// @Success 200 {object} reference.{{.ResponseModel}} +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.PutPath}} [put] +{{end}} +func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + {{if $.HasLogger}} + h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + + {{range .PathParams}} + {{.}} := c.Param("{{.}}") + if {{.}} == "" { + {{if $.HasLogger}} + h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter {{.}}", + RequestID: requestID, + }) + return + } + {{end}} + + var req reference.{{.Model}} + if err := c.ShouldBindJSON(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + {{if $.HasLogger}} + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.{{.ResponseModel}} + {{if .PathParams}} + result, err := h.service.Update{{.Name}}(ctx{{range .PathParams}}, {{.}}{{end}}, &req) + {{else}} + result, err := h.service.Update{{.Name}}(ctx, &req) + {{end}} + if err != nil { + {{if $.HasLogger}} + h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusOK, response) +} +{{end}} + +{{if .HasDelete}} +// Delete{{.NameUpper}} deletes existing {{.Name}} +{{if $.HasSwagger}} +// @Summary Delete {{.Name}} +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @Accept json +// @Produce json +{{if .RequireAuth}} +// @Security ApiKeyAuth +{{end}} +{{range .PathParams}} +// @Param {{.}} path string true "{{.}}" +{{end}} +// @Success 204 {object} nil +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router {{.DeletePath}} [delete] +{{end}} +func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + {{if $.HasLogger}} + h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + + {{range .PathParams}} + {{.}} := c.Param("{{.}}") + if {{.}} == "" { + {{if $.HasLogger}} + h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter {{.}}", + RequestID: requestID, + }) + return + } + {{end}} + + // Call service method + {{if .PathParams}} + err := h.service.Delete{{.Name}}(ctx{{range .PathParams}}, {{.}}{{end}}) + {{else}} + err := h.service.Delete{{.Name}}(ctx) + {{end}} + if err != nil { + {{if $.HasLogger}} + h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + {{end}} + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + c.Status(http.StatusNoContent) +} +{{end}} {{end}} ` diff --git a/vclaimhandlerexemple b/vclaimhandlerexemple new file mode 100644 index 0000000..652003e --- /dev/null +++ b/vclaimhandlerexemple @@ -0,0 +1,414 @@ +// Code generated by generate-dynamic-handler.go; DO NOT EDIT. +// Generated at: 2025-08-29 13:02:57 +// Service: VClaim (vclaim) +// Description: BPJS VClaim service for eligibility and SEP management + +package handlers + +import ( + "context" + "fmt" + "net/http" + "time" + + "api-service/internal/config" + "api-service/internal/models/reference" + services "api-service/internal/services/bpjs" + "api-service/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// VClaimHandler handles VClaim BPJS services +type VClaimHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// VClaimHandlerConfig contains configuration for VClaimHandler +type VClaimHandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// NewVClaimHandler creates a new VClaimHandler +func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { + return &VClaimHandler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +// GetPESERTA retrieves Peserta data +// @Summary Get Peserta data +// @Description Get participant eligibility information +// @Tags vclaim,peserta +// @Accept json +// @Produce json +// @Param nokartu path string true "nokartu" +// @Success 200 {object} reference.PesertaResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /peserta/:nokartu [get] + +func (h *VClaimHandler) GetPESERTA(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*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) + } + + h.logger.Info("Processing GetPeserta request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/peserta/:nokartu", + + "nokartu": c.Param("nokartu"), + }) + + // Extract path parameters + + nokartu := c.Param("nokartu") + if nokartu == "" { + + h.logger.Error("Missing required parameter nokartu", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nokartu", + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.PesertaResponse + + result, err := h.GetPeserta(ctx, nokartu) + if err != nil { + h.logger.Error("Failed to get Peserta", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Assign result to response + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusOK, response) +} + +// GetSEP retrieves Sep data + +// @Summary Get Sep data +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json +// @Param nosep path string true "nosep" +// @Success 200 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep/:nosep [get] + +func (h *VClaimHandler) GetSEPHandler(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*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) + } + + h.logger.Info("Processing GetSep request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/sep/:nosep", + + "nosep": c.Param("nosep"), + }) + + // Extract path parameters + + nosep := c.Param("nosep") + if nosep == "" { + + h.logger.Error("Missing required parameter nosep", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Missing required parameter nosep", + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.SEPResponse + + result, err := h.GetSEP(ctx, nosep) + if err != nil { + h.logger.Error("Failed to get Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Assign result to response + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusOK, response) +} + +// CreateSEP creates new Sep + +// @Summary Create Sep +// @Description Manage SEP (Surat Eligibilitas Peserta) +// @Tags vclaim,sep +// @Accept json +// @Produce json +// @Param request body reference.SEPRequest true "Sep data" +// @Success 201 {object} reference.SEPResponse +// @Failure 400 {object} reference.ErrorResponse +// @Failure 500 {object} reference.ErrorResponse +// @Router /sep [post] + +func (h *VClaimHandler) CreateSEPHandler(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } + + h.logger.Info("Processing CreateSep request", map[string]interface{}{ + "request_id": requestID, + }) + + var req reference.SEPRequest + if err := c.ShouldBindJSON(&req); err != nil { + + h.logger.Error("Invalid request body", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Invalid request body: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Validate request + if err := h.validator.Struct(&req); err != nil { + + h.logger.Error("Validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, reference.ErrorResponse{ + Status: "error", + Message: "Validation failed: " + err.Error(), + RequestID: requestID, + }) + return + } + + // Call service method + var response reference.SEPResponse + result, err := h.CreateSEP(ctx, &req) + if err != nil { + h.logger.Error("Failed to create Sep", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Assign result to response + response.Status = "success" + response.RequestID = requestID + response.Data = result + c.JSON(http.StatusCreated, response) +} + +// Handler for Helpers + +// GetPeserta implements reference.VClaimService.GetPeserta +func (h *VClaimHandler) GetPeserta(ctx context.Context, noKartu string) (*reference.PesertaData, error) { + var response struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response reference.PesertaData `json:"response"` + } + + endpoint := fmt.Sprintf("/Peserta/%s", noKartu) + err := h.service.Get(ctx, endpoint, &response) + if err != nil { + return nil, err + } + + if response.MetaData.Code != "200" { + return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) + } + + return &response.Response, nil +} + +// GetSEP implements reference.VClaimService.GetSEP +func (h *VClaimHandler) GetSEP(ctx context.Context, noSep string) (*reference.SEPData, error) { + var response struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response reference.SEPData `json:"response"` + } + + endpoint := fmt.Sprintf("/SEP/%s", noSep) + err := h.service.Get(ctx, endpoint, &response) + if err != nil { + return nil, err + } + + if response.MetaData.Code != "200" { + return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) + } + + return &response.Response, nil +} + +// CreateSEP implements reference.VClaimService.CreateSEP +func (h *VClaimHandler) CreateSEP(ctx context.Context, req *reference.SEPRequest) (*reference.SEPData, error) { + var response struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response reference.SEPData `json:"response"` + } + + endpoint := "/SEP/2.0/insert" + err := h.service.Post(ctx, endpoint, req, &response) + if err != nil { + return nil, err + } + + if response.MetaData.Code != "200" { + return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) + } + + return &response.Response, nil +} + +// UpdateSEP implements reference.VClaimService.UpdateSEP +func (h *VClaimHandler) UpdateSEP(ctx context.Context, noSep string, req *reference.SEPRequest) (*reference.SEPData, error) { + var response struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response reference.SEPData `json:"response"` + } + + endpoint := fmt.Sprintf("/SEP/%s", noSep) + err := h.service.Put(ctx, endpoint, req, &response) + if err != nil { + return nil, err + } + + if response.MetaData.Code != "200" { + return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) + } + + return &response.Response, nil +} + +// DeleteSEP implements reference.VClaimService.DeleteSEP +func (h *VClaimHandler) DeleteSEP(ctx context.Context, noSep string) error { + var response struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + } + + endpoint := fmt.Sprintf("/SEP/%s", noSep) + err := h.service.Delete(ctx, endpoint, &response) + if err != nil { + return err + } + + if response.MetaData.Code != "200" { + return fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) + } + + return nil +} + +// GetRujukan implements reference.VClaimService.GetRujukan +func (h *VClaimHandler) GetRujukan(ctx context.Context, noRujukan string) (*reference.RujukanData, error) { + var response struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response reference.RujukanData `json:"response"` + } + + endpoint := fmt.Sprintf("/Rujukan/%s", noRujukan) + err := h.service.Get(ctx, endpoint, &response) + if err != nil { + return nil, err + } + + if response.MetaData.Code != "200" { + return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) + } + + return &response.Response, nil +}