From 40af041bc94db7501ca00b172ddc046eeff348f5 Mon Sep 17 00:00:00 2001 From: Meninjar Date: Mon, 25 Aug 2025 04:34:47 +0700 Subject: [PATCH] pembaruhan swagger dan tool --- README.md | 8 +- internal/handlers/antrol/peserta.go | 533 ++++++++++++++ internal/handlers/vclaim/peserta.go | 164 +++++ internal/models/antrol/peserta.go | 197 ++++++ internal/models/vclaim/peserta.go | 66 ++ pkg/validator/validator | 1 + tools/bpjs/generate-bpjs-handler.go | 957 +++++++++++++++++++------- tools/generate-bpjs-handler.go.backup | 644 +++++++++++++++++ 8 files changed, 2332 insertions(+), 238 deletions(-) create mode 100644 internal/handlers/antrol/peserta.go create mode 100644 internal/handlers/vclaim/peserta.go create mode 100644 internal/models/antrol/peserta.go create mode 100644 internal/models/vclaim/peserta.go create mode 100644 tools/generate-bpjs-handler.go.backup diff --git a/README.md b/README.md index 01749e9..f0b6003 100644 --- a/README.md +++ b/README.md @@ -80,13 +80,13 @@ tools/generate.bat product get post put delete ./tools/generate.sh product get post put delete # Atau langsung dengan Go -go run tools/generate-handler.go orders get post +go run tools/general/generate-handler.go orders get post -go run tools/generate-handler.go orders/product get post +go run tools/general/generate-handler.go orders/product get post -go run tools/generate-handler.go orders/order get post put delete dynamic search stats +go run tools/general/generate-handler.go orders/order get post put delete dynamic search stats -go run tools/generate-bpjs-handler.go reference/peserta get +go run tools/bpjs/generate-bpjs-handler.go reference/peserta get ``` ### Method Tersedia diff --git a/internal/handlers/antrol/peserta.go b/internal/handlers/antrol/peserta.go new file mode 100644 index 0000000..f4bd389 --- /dev/null +++ b/internal/handlers/antrol/peserta.go @@ -0,0 +1,533 @@ +package handlers + +import ( + "context" + "fmt" + "net/http" + "time" + + "api-service/internal/config" + models "api-service/internal/models/antrol" + 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" +) + +// PesertaHandler handles peserta BPJS services with optimized error handling and logging +type PesertaHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config *config.BpjsConfig +} + +// HandlerConfig contains configuration for PesertaHandler +type PesertaHandlerConfig struct { + BpjsConfig *config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// NewPesertaHandler creates a new optimized PesertaHandler +func NewPesertaHandler(cfg *PesertaHandlerConfig) *PesertaHandler { + return &PesertaHandler{ + service: services.NewService(*cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + +// CreatePeserta creates a new Peserta with comprehensive error handling and validation +// @Summary Create a new PESERTA +// @Description Create a new Peserta in BPJS system with enhanced validation and logging +// @Tags Antrol-Peserta +// @Accept json +// @Produce json +// @Param request body models.PesertaPostRequest true "Peserta creation request" +// @Success 200 {object} models.PesertaResponse "Peserta created successfully" +// @Failure 400 {object} models.PesertaResponse "Bad request - validation error" +// @Failure 422 {object} models.PesertaResponse "Unprocessable entity - business logic error" +// @Failure 500 {object} models.PesertaResponse "Internal server error" +// @Router /api/v1/antrol/peserta [post] +func (h *PesertaHandler) CreatePeserta(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + + h.logger.Info("Creating Peserta", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + }) + + var req models.PesertaPostRequest + req.RequestID = requestID + req.Timestamp = startTime + + // Bind and validate JSON + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Failed to bind JSON", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", + "Format request tidak valid", err.Error(), requestID) + return + } + + // Custom validation + if err := req.Validate(); err != nil { + h.logger.Error("Custom validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + // Struct validation + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Struct validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi struktur gagal", h.formatValidationError(err), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Post(ctx, "PESERTA/2.0/insert", req, &rawResponse); err != nil { + h.logger.Error("Failed to call BPJS service", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "endpoint": "PESERTA/2.0/insert", + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal membuat Peserta", err.Error(), requestID) + return + } + + // Check BPJS response + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("Peserta created successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + }) + + h.sendSuccessResponse(c, "Peserta berhasil dibuat", rawResponse.Response, requestID) +} + +// UpdatePeserta updates an existing Peserta with comprehensive validation +// @Summary Update an existing PESERTA +// @Description Update an existing Peserta in BPJS system with enhanced validation and logging +// @Tags Antrol-Peserta +// @Accept json +// @Produce json +// @Param request body models.PesertaPutRequest true "Peserta update request" +// @Success 200 {object} models.PesertaResponse "Peserta updated successfully" +// @Failure 400 {object} models.PesertaResponse "Bad request - validation error" +// @Failure 422 {object} models.PesertaResponse "Unprocessable entity - business logic error" +// @Failure 500 {object} models.PesertaResponse "Internal server error" +// @Router /api/v1/antrol/peserta [put] +func (h *PesertaHandler) UpdatePeserta(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + + h.logger.Info("Updating Peserta", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + }) + + var req models.PesertaPutRequest + req.RequestID = requestID + req.Timestamp = startTime + + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Failed to bind JSON for update", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", + "Format request tidak valid", err.Error(), requestID) + return + } + + if err := req.Validate(); err != nil { + h.logger.Error("Update validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Struct validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi struktur gagal", h.formatValidationError(err), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Put(ctx, "PESERTA/2.0/update", req, &rawResponse); err != nil { + h.logger.Error("Failed to update Peserta", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal memperbarui Peserta", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS update returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("Peserta updated successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + }) + + h.sendSuccessResponse(c, "Peserta berhasil diperbarui", rawResponse.Response, requestID) +} + +// DeletePeserta deletes an existing Peserta with comprehensive validation +// @Summary Delete an existing PESERTA +// @Description Delete a Peserta by ID with enhanced validation and logging +// @Tags Antrol-Peserta +// @Accept json +// @Produce json +// @Param id path string true "Peserta ID" +// @Param user query string true "User identifier" +// @Success 200 {object} models.PesertaResponse "Peserta deleted successfully" +// @Failure 400 {object} models.PesertaResponse "Bad request - missing parameters" +// @Failure 422 {object} models.PesertaResponse "Unprocessable entity - business logic error" +// @Failure 500 {object} models.PesertaResponse "Internal server error" +// @Router /api/v1/antrol/peserta/{id} [delete] +func (h *PesertaHandler) DeletePeserta(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + id := c.Param("id") + user := c.Query("user") + + h.logger.Info("Deleting Peserta", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + "id": id, + "user": user, + }) + + // Validate parameters + if id == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter ID wajib diisi", "", requestID) + return + } + + if user == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter user wajib diisi", "", requestID) + return + } + + req := models.PesertaDeleteRequest{ + BaseRequest: models.BaseRequest{ + RequestID: requestID, + Timestamp: startTime, + }, + TPeserta: models.PesertaDeleteData{ + ID: id, + User: user, + }, + } + + if err := req.Validate(); err != nil { + h.logger.Error("Delete validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Delete(ctx, "PESERTA/2.0/delete", req); err != nil { + h.logger.Error("Failed to delete Peserta", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal menghapus Peserta", err.Error(), requestID) + return + } + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS delete returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + "id": id, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("Peserta deleted successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + "id": id, + }) + + h.sendSuccessResponse(c, "Peserta berhasil dihapus", rawResponse.Response, requestID) +} + +// GetPeserta retrieves Peserta details with comprehensive error handling +// @Summary Get an existing PESERTA +// @Description Retrieve a Peserta by ID with enhanced validation and logging +// @Tags Antrol-Peserta +// @Accept json +// @Produce json +// @Param id path string true "Peserta ID" +// @Success 200 {object} models.PesertaResponse "Data Peserta retrieved successfully" +// @Failure 400 {object} models.PesertaResponse "Bad request - invalid ID" +// @Failure 404 {object} models.PesertaResponse "Peserta not found" +// @Failure 500 {object} models.PesertaResponse "Internal server error" +// @Router /api/v1/antrol/peserta/{id} [get] +func (h *PesertaHandler) GetPeserta(c *gin.Context) { + requestID := uuid.New().String() + startTime := time.Now() + id := c.Param("id") + + h.logger.Info("Getting Peserta", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + "id": id, + }) + + if id == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter ID wajib diisi", "", requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + endpoint := fmt.Sprintf("PESERTA/%s", id) + var rawResponse models.BpjsRawResponse + + if err := h.service.Get(ctx, endpoint, &rawResponse); err != nil { + h.logger.Error("Failed to get Peserta", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "id": id, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal mengambil data Peserta", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + // Handle specific BPJS error codes + if rawResponse.MetaData.Code == "201" { + h.logger.Info("Peserta not found", map[string]interface{}{ + "request_id": requestID, + "id": id, + }) + h.sendErrorResponse(c, http.StatusNotFound, "DATA_NOT_FOUND", + "Data Peserta tidak ditemukan", rawResponse.MetaData.Message, requestID) + return + } + + h.logger.Warn("BPJS get returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + "id": id, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("Peserta retrieved successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + "id": id, + }) + + h.sendSuccessResponse(c, "Data Peserta berhasil diambil", rawResponse.Response, requestID) +} + +// Helper methods for PesertaHandler +func (h *PesertaHandler) sendSuccessResponse(c *gin.Context, message string, data interface{}, requestID string) { + response := models.PesertaResponse{ + BaseResponse: models.BaseResponse{ + Status: "success", + Message: message, + Data: data, + Metadata: &models.ResponseMetadata{ + Timestamp: time.Now(), + Version: "2.0", + RequestID: requestID, + }, + }, + } + c.JSON(http.StatusOK, response) +} + +func (h *PesertaHandler) sendErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { + response := models.PesertaResponse{ + BaseResponse: models.BaseResponse{ + Status: "error", + Message: message, + Error: &models.ErrorResponse{ + Code: errorCode, + Message: message, + Details: details, + }, + Metadata: &models.ResponseMetadata{ + Timestamp: time.Now(), + Version: "2.0", + RequestID: requestID, + }, + }, + } + c.JSON(statusCode, response) +} + +func (h *PesertaHandler) formatValidationError(err error) string { + if validationErrors, ok := err.(validator.ValidationErrors); ok { + var messages []string + for _, e := range validationErrors { + switch e.Tag() { + case "required": + messages = append(messages, fmt.Sprintf("%s wajib diisi", e.Field())) + case "min": + messages = append(messages, fmt.Sprintf("%s minimal %s karakter", e.Field(), e.Param())) + case "max": + messages = append(messages, fmt.Sprintf("%s maksimal %s karakter", e.Field(), e.Param())) + case "oneof": + messages = append(messages, fmt.Sprintf("%s harus salah satu dari: %s", e.Field(), e.Param())) + default: + messages = append(messages, fmt.Sprintf("%s tidak valid", e.Field())) + } + } + return fmt.Sprintf("Validasi gagal: %v", messages) + } + return err.Error() +} + +func (h *PesertaHandler) categorizeError(err error) (int, string) { + if err == nil { + return http.StatusOK, "SUCCESS" + } + + errStr := err.Error() + + if h.isTimeoutError(err) { + return http.StatusRequestTimeout, "REQUEST_TIMEOUT" + } + + if h.isNetworkError(err) { + return http.StatusBadGateway, "NETWORK_ERROR" + } + + if h.isAuthError(errStr) { + return http.StatusUnauthorized, "AUTH_ERROR" + } + + return http.StatusInternalServerError, "INTERNAL_ERROR" +} + +func (h *PesertaHandler) mapBpjsCodeToHttpStatus(bpjsCode string) int { + switch bpjsCode { + case "200": + return http.StatusOK + case "201": + return http.StatusNotFound + case "202": + return http.StatusBadRequest + case "400": + return http.StatusBadRequest + case "401": + return http.StatusUnauthorized + case "403": + return http.StatusForbidden + case "404": + return http.StatusNotFound + case "500": + return http.StatusInternalServerError + default: + return http.StatusUnprocessableEntity + } +} + +func (h *PesertaHandler) isTimeoutError(err error) bool { + return err != nil && (err.Error() == "context deadline exceeded" || + err.Error() == "timeout") +} + +func (h *PesertaHandler) isNetworkError(err error) bool { + return err != nil && (err.Error() == "connection refused" || + err.Error() == "no such host") +} + +func (h *PesertaHandler) isAuthError(errStr string) bool { + return errStr == "unauthorized" || errStr == "invalid credentials" +} diff --git a/internal/handlers/vclaim/peserta.go b/internal/handlers/vclaim/peserta.go new file mode 100644 index 0000000..1c941d1 --- /dev/null +++ b/internal/handlers/vclaim/peserta.go @@ -0,0 +1,164 @@ +package handlers + +import ( + "context" + "fmt" + "net/http" + "time" + + "api-service/internal/config" + models "api-service/internal/models/vclaim" + services "api-service/internal/services/bpjs" + + "github.com/gin-gonic/gin" +) + +// PesertaHandler handles peserta BPJS services +type PesertaHandler struct { + service services.VClaimService +} + +// NewPesertaHandler creates a new PesertaHandler +func NewPesertaHandler(cfg config.BpjsConfig) *PesertaHandler { + return &PesertaHandler{ + service: services.NewService(cfg), + } +} + +// CreatePeserta godoc +// @Summary Create a new PESERTA +// @Description Create a new Peserta in BPJS system +// @Tags vclaim-peserta +// @Accept json +// @Produce json +// @Param request body models.PesertaPostRequest true "Peserta creation request" +// @Success 200 {object} models.PesertaResponse "Peserta created successfully" +// @Failure 400 {object} gin.H "Invalid request" +// @Failure 500 {object} gin.H "Internal server error" +// @Router /api/v1/vclaim/peserta [post] +func (h *PesertaHandler) CreatePeserta(c *gin.Context) { + var req models.PesertaPostRequest + 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, "PESERTA/2.0/insert", req, &result); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed", "message": err.Error()}) + return + } + + c.JSON(http.StatusOK, models.PesertaResponse{ + Message: "Peserta berhasil dibuat", + Data: result, + }) +} + +// UpdatePeserta godoc +// @Summary Update an existing PESERTA +// @Description Update an existing Peserta in BPJS system +// @Tags vclaim-peserta +// @Accept json +// @Produce json +// @Param request body models.PesertaPutRequest true "Peserta update request" +// @Success 200 {object} models.PesertaResponse "Peserta updated successfully" +// @Failure 400 {object} gin.H "Invalid request" +// @Failure 500 {object} gin.H "Internal server error" +// @Router /api/v1/vclaim/peserta [put] +func (h *PesertaHandler) UpdatePeserta(c *gin.Context) { + var req models.PesertaPutRequest + 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, "PESERTA/2.0/update", req, &result); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed", "message": err.Error()}) + return + } + + c.JSON(http.StatusOK, models.PesertaResponse{ + Message: "Peserta berhasil diperbarui", + Data: result, + }) +} + +// DeletePeserta godoc +// @Summary Delete an existing PESERTA +// @Description Delete a Peserta by ID +// @Tags vclaim-peserta +// @Accept json +// @Produce json +// @Param id path string true "Peserta ID" +// @Param user query string true "User" +// @Success 200 {object} models.PesertaResponse "Peserta deleted successfully" +// @Failure 400 {object} gin.H "Invalid request" +// @Failure 500 {object} gin.H "Internal server error" +// @Router /api/v1/vclaim/peserta/{id} [delete] +func (h *PesertaHandler) DeletePeserta(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.PesertaDeleteRequest{} + body.TPeserta.ID = id + body.TPeserta.User = user + + ctx, cancel := context.WithTimeout(c, 30*time.Second) + defer cancel() + + if err := h.service.Delete(ctx, "PESERTA/2.0/delete", body); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed", "message": err.Error()}) + return + } + + c.JSON(http.StatusOK, models.PesertaResponse{ + Message: "Peserta berhasil dihapus", + Data: nil, + }) +} + +// GetPeserta godoc +// @Summary Get an existing PESERTA +// @Description Retrieve a Peserta by ID +// @Tags vclaim-peserta +// @Accept json +// @Produce json +// @Param id path string true "Peserta ID" +// @Success 200 {object} models.PesertaResponse "Data Peserta retrieved successfully" +// @Failure 400 {object} gin.H "Invalid request" +// @Failure 500 {object} gin.H "Internal server error" +// @Router /api/v1/vclaim/peserta/{id} [get] +func (h *PesertaHandler) GetPeserta(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("PESERTA/%s", 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.PesertaResponse{ + Message: "Data Peserta berhasil diambil", + Data: result, + }) +} \ No newline at end of file diff --git a/internal/models/antrol/peserta.go b/internal/models/antrol/peserta.go new file mode 100644 index 0000000..a9f954c --- /dev/null +++ b/internal/models/antrol/peserta.go @@ -0,0 +1,197 @@ +package models + +import ( + "encoding/json" + "fmt" + "time" +) + +// Peserta BPJS Models with Enhanced Validation +// Generated at: 2025-08-24 20:09:32 +// Category: antrol + +// Base request/response structures +type BaseRequest struct { + RequestID string `json:"request_id,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty"` +} + +type BaseResponse struct { + Status string `json:"status"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` + Error *ErrorResponse `json:"error,omitempty"` + Metadata *ResponseMetadata `json:"metadata,omitempty"` +} + +type ErrorResponse struct { + Code string `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +type ResponseMetadata struct { + Timestamp time.Time `json:"timestamp"` + Version string `json:"version"` + RequestID string `json:"request_id,omitempty"` +} + +// Peserta Response Structure +type PesertaResponse struct { + BaseResponse +} + +// BPJS Raw Response Structure +type BpjsRawResponse struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response interface{} `json:"response"` +} + +// Peserta POST Request Structure with Enhanced Validation +type PesertaPostRequest struct { + BaseRequest + TPeserta PesertaPost `json:"t_sep" binding:"required" validate:"required"` +} + +type PesertaPost struct { + // Core BPJS fields - customize based on your specific requirements + NoKartu string `json:"noKartu" binding:"required" validate:"required,min=13,max=13"` + TglLayanan string `json:"tglLayanan" binding:"required" validate:"required"` + JnsPelayanan string `json:"jnsPelayanan" binding:"required" validate:"required,oneof=1 2"` + PpkPelayanan string `json:"ppkPelayanan" binding:"required" validate:"required"` + Catatan string `json:"catatan" validate:"omitempty,max=200"` + User string `json:"user" binding:"required" validate:"required"` +} + +// Validate validates the PesertaPostRequest +func (r *PesertaPostRequest) Validate() error { + if r.TPeserta.NoKartu == "" { + return fmt.Errorf("nomor kartu tidak boleh kosong") + } + + if len(r.TPeserta.NoKartu) != 13 { + return fmt.Errorf("nomor kartu harus 13 digit") + } + + if _, err := time.Parse("2006-01-02", r.TPeserta.TglLayanan); err != nil { + return fmt.Errorf("format tanggal layanan tidak valid, gunakan yyyy-MM-dd") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *PesertaPostRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err +} + +// Peserta PUT Request Structure with Enhanced Validation +type PesertaPutRequest struct { + BaseRequest + TPeserta PesertaPut `json:"t_sep" binding:"required" validate:"required"` +} + +type PesertaPut struct { + ID string `json:"id" binding:"required" validate:"required"` + NoKartu string `json:"noKartu" validate:"omitempty,min=13,max=13"` + TglLayanan string `json:"tglLayanan" validate:"omitempty"` + JnsPelayanan string `json:"jnsPelayanan" validate:"omitempty,oneof=1 2"` + PpkPelayanan string `json:"ppkPelayanan" validate:"omitempty"` + Catatan string `json:"catatan" validate:"omitempty,max=200"` + User string `json:"user" binding:"required" validate:"required"` +} + +// Validate validates the PesertaPutRequest +func (r *PesertaPutRequest) Validate() error { + if r.TPeserta.ID == "" { + return fmt.Errorf("ID tidak boleh kosong") + } + + if r.TPeserta.NoKartu != "" && len(r.TPeserta.NoKartu) != 13 { + return fmt.Errorf("nomor kartu harus 13 digit") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *PesertaPutRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err +} + +// Peserta DELETE Request Structure with Enhanced Validation +type PesertaDeleteRequest struct { + BaseRequest + TPeserta PesertaDeleteData `json:"t_sep" binding:"required" validate:"required"` +} + +type PesertaDeleteData struct { + ID string `json:"id" binding:"required" validate:"required"` + User string `json:"user" binding:"required" validate:"required"` +} + +// Validate validates the PesertaDeleteRequest +func (r *PesertaDeleteRequest) Validate() error { + if r.TPeserta.ID == "" { + return fmt.Errorf("ID tidak boleh kosong") + } + + if r.TPeserta.User == "" { + return fmt.Errorf("User tidak boleh kosong") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *PesertaDeleteRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err +} + +// Common Helper Structures for BPJS +type Flag struct { + Flag string `json:"flag" binding:"required" validate:"required,oneof=0 1"` +} + +type Poli struct { + Tujuan string `json:"tujuan" binding:"required" validate:"required"` + Eksekutif string `json:"eksekutif" binding:"required" validate:"required,oneof=0 1"` +} + +type KlsRawat struct { + KlsRawatHak string `json:"klsRawatHak" binding:"required" validate:"required,oneof=1 2 3"` + KlsRawatNaik string `json:"klsRawatNaik" validate:"omitempty,oneof=1 2 3 4 5 6 7"` + Pembiayaan string `json:"pembiayaan" validate:"omitempty,oneof=1 2 3"` + PenanggungJawab string `json:"penanggungJawab" validate:"omitempty,max=100"` +} + +// Validation helper functions +func IsValidStatus(status string) bool { + validStatuses := []string{"active", "inactive", "pending", "processed"} + for _, v := range validStatuses { + if v == status { + return true + } + } + return false +} + +func IsValidJnsPelayanan(jns string) bool { + return jns == "1" || jns == "2" // 1: rawat jalan, 2: rawat inap +} + +func IsValidKlsRawat(kls string) bool { + validKelas := []string{"1", "2", "3"} + for _, v := range validKelas { + if v == kls { + return true + } + } + return false +} diff --git a/internal/models/vclaim/peserta.go b/internal/models/vclaim/peserta.go new file mode 100644 index 0000000..ee64a09 --- /dev/null +++ b/internal/models/vclaim/peserta.go @@ -0,0 +1,66 @@ +package models + +// Peserta BPJS Models +// Generated at: 2025-08-24 16:39:05 +// Category: vclaim + +// Common Response Structure +type PesertaResponse struct { + Message string `json:"message"` + Data map[string]interface{} `json:"data,omitempty"` +} + +type PesertaRawResponse struct { + MetaData struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"metaData"` + Response interface{} `json:"response"` +} + +// Peserta POST Request Structure +type PesertaPostRequest struct { + TPeserta PesertaPost `json:"t_peserta" binding:"required"` +} + +type PesertaPost 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"` +} + +// Peserta PUT Request Structure +type PesertaPutRequest struct { + TPeserta PesertaPut `json:"t_peserta" binding:"required"` +} + +type PesertaPut 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"` +} + +// Peserta DELETE Request Structure +type PesertaDeleteRequest struct { + TPeserta struct { + ID string `json:"id" binding:"required"` + User string `json:"user" binding:"required"` + } `json:"t_peserta" binding:"required"` +} + +// Common Helper Structures + +// Validation helpers +func IsValidStatus(status string) bool { + validStatuses := []string{"active", "inactive", "pending", "processed"} + for _, v := range validStatuses { + if v == status { + return true + } + } + return false +} diff --git a/pkg/validator/validator b/pkg/validator/validator index e69de29..8b13789 100644 --- a/pkg/validator/validator +++ b/pkg/validator/validator @@ -0,0 +1 @@ + diff --git a/tools/bpjs/generate-bpjs-handler.go b/tools/bpjs/generate-bpjs-handler.go index e6c3c66..ab32129 100644 --- a/tools/bpjs/generate-bpjs-handler.go +++ b/tools/bpjs/generate-bpjs-handler.go @@ -31,19 +31,18 @@ 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 vclaim/sep get post put delete") + fmt.Println(" go run generate-bpjs-handler.go eclaim/klaim get post") fmt.Println(" go run generate-bpjs-handler.go peserta get") os.Exit(1) } - // Parse entity path (could be "entity" or "category/entity") + // Parse entity path 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"} } @@ -63,7 +62,7 @@ func main() { } // Format names - entityName = strings.Title(entityName) // PascalCase entity name + entityName = strings.Title(entityName) entityLower := strings.ToLower(entityName) entityUpper := strings.ToUpper(entityName) @@ -77,37 +76,34 @@ func main() { Timestamp: time.Now().Format("2006-01-02 15:04:05"), } - // Set methods and endpoints based on arguments + // Set methods and endpoints for _, m := range methods { switch strings.ToLower(m) { case "get": data.HasGet = true - data.GetEndpoint = fmt.Sprintf("/%s/{id}", entityUpper) + data.GetEndpoint = fmt.Sprintf("%s/{id}", entityUpper) case "post": data.HasPost = true - data.PostEndpoint = fmt.Sprintf("/%s/2.0/insert", entityUpper) + data.PostEndpoint = fmt.Sprintf("%s/2.0/insert", entityUpper) case "put": data.HasPut = true - data.PutEndpoint = fmt.Sprintf("/%s/2.0/update", entityUpper) + data.PutEndpoint = fmt.Sprintf("%s/2.0/update", entityUpper) case "delete": data.HasDelete = true - data.DeleteEndpoint = fmt.Sprintf("/%s/2.0/delete", entityUpper) + data.DeleteEndpoint = fmt.Sprintf("%s/2.0/delete", entityUpper) } } - // Create directories with dynamic logic (sama seperti generate-handler.go) + // Create directories 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) @@ -115,11 +111,11 @@ func main() { } // Generate files - generateBpjsHandlerFile(data, handlerDir) - generateBpjsModelFile(data, modelDir) - updateBpjsRoutesFile(data) + generateOptimizedBpjsHandlerFile(data, handlerDir) + generateOptimizedBpjsModelFile(data, modelDir) + updateOptimizedBpjsRoutesFile(data) - fmt.Printf("✅ Successfully generated BPJS handler: %s\n", entityName) + fmt.Printf("✅ Successfully generated optimized BPJS handler: %s\n", entityName) if category != "" { fmt.Printf("📁 Category: %s\n", category) } @@ -127,10 +123,9 @@ func main() { fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go")) } -// ================= HANDLER GENERATION ===================== +// ================= OPTIMIZED HANDLER GENERATION ===================== -func generateBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { - // Build import path based on category (sama seperti generate-handler.go) +func generateOptimizedBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { var modelsImportPath string if data.Category != "" { modelsImportPath = data.ModuleName + "/internal/models/" + data.Category @@ -141,373 +136,852 @@ func generateBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { handlerContent := `package handlers import ( - "context" - "fmt" - "net/http" - "time" + "context" + "fmt" + "net/http" + "time" - "` + data.ModuleName + `/internal/config" - models "` + modelsImportPath + `" - services "` + data.ModuleName + `/internal/services/bpjs" + "` + data.ModuleName + `/internal/config" + "` + modelsImportPath + `" + services "` + data.ModuleName + `/internal/services/bpjs" + "` + data.ModuleName + `/pkg/logger" + "` + data.ModuleName + `/pkg/validator" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/go-playground/validator/v10" ) -// ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services +// ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services with optimized error handling and logging type ` + data.Name + `Handler struct { - service services.VClaimService + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config *config.BpjsConfig } -// 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), - } +// HandlerConfig contains configuration for ` + data.Name + `Handler +type ` + data.Name + `HandlerConfig struct { + BpjsConfig *config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler +func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler { + return &` + data.Name + `Handler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } }` - // Add methods based on flags + // Add optimized methods based on flags if data.HasPost { - handlerContent += generateBpjsCreateMethod(data) + handlerContent += generateOptimizedBpjsCreateMethod(data) } if data.HasPut { - handlerContent += generateBpjsUpdateMethod(data) + handlerContent += generateOptimizedBpjsUpdateMethod(data) } if data.HasDelete { - handlerContent += generateBpjsDeleteMethod(data) + handlerContent += generateOptimizedBpjsDeleteMethod(data) } if data.HasGet { - handlerContent += generateBpjsGetMethod(data) + handlerContent += generateOptimizedBpjsGetMethod(data) } - writeFileBpjs(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) + // Add helper methods + handlerContent += generateHelperMethods(data) + + writeFile(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 +func generateOptimizedBpjsCreateMethod(data BpjsHandlerData) string { + var routePath, tagName string if data.Category != "" { routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) } else { routePath = data.NameLower - } - - // Tag untuk swagger - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower + tagName = strings.Title(data.NameLower) } return ` -// Create` + data.Name + ` godoc +// Create` + data.Name + ` creates a new ` + data.Name + ` with comprehensive error handling and validation // @Summary Create a new ` + data.NameUpper + ` -// @Description Create a new ` + data.Name + ` in BPJS system +// @Description Create a new ` + data.Name + ` in BPJS system with enhanced validation and logging // @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" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" +// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" +// @Failure 500 {object} models.` + data.Name + `Response "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 - } + requestID := uuid.New().String() + startTime := time.Now() + + h.logger.Info("Creating ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + }) - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() + var req models.` + data.Name + `PostRequest + req.RequestID = requestID + req.Timestamp = startTime - 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 - } + // Bind and validate JSON + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Failed to bind JSON", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", + "Format request tidak valid", err.Error(), requestID) + return + } - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "` + data.Name + ` berhasil dibuat", - Data: result, - }) + // Custom validation + if err := req.Validate(); err != nil { + h.logger.Error("Custom validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + // Struct validation + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Struct validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi struktur gagal", h.formatValidationError(err), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Post(ctx, "` + data.PostEndpoint + `", req, &rawResponse); err != nil { + h.logger.Error("Failed to call BPJS service", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "endpoint": "` + data.PostEndpoint + `", + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal membuat ` + data.Name + `", err.Error(), requestID) + return + } + + // Check BPJS response + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` created successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + }) + + h.sendSuccessResponse(c, "` + data.Name + ` berhasil dibuat", rawResponse.Response, requestID) }` } -func generateBpjsUpdateMethod(data BpjsHandlerData) string { - var routePath string +func generateOptimizedBpjsUpdateMethod(data BpjsHandlerData) string { + var routePath, tagName string if data.Category != "" { routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) } else { routePath = data.NameLower - } - - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower + tagName = strings.Title(data.NameLower) } return ` -// Update` + data.Name + ` godoc +// Update` + data.Name + ` updates an existing ` + data.Name + ` with comprehensive validation // @Summary Update an existing ` + data.NameUpper + ` -// @Description Update an existing ` + data.Name + ` in BPJS system +// @Description Update an existing ` + data.Name + ` in BPJS system with enhanced validation and logging // @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" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" +// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" +// @Failure 500 {object} models.` + data.Name + `Response "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 - } + requestID := uuid.New().String() + startTime := time.Now() + + h.logger.Info("Updating ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + }) - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() + var req models.` + data.Name + `PutRequest + req.RequestID = requestID + req.Timestamp = startTime - 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 - } + if err := c.ShouldBindJSON(&req); err != nil { + h.logger.Error("Failed to bind JSON for update", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", + "Format request tidak valid", err.Error(), requestID) + return + } - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "` + data.Name + ` berhasil diperbarui", - Data: result, - }) + if err := req.Validate(); err != nil { + h.logger.Error("Update validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + if err := h.validator.Struct(&req); err != nil { + h.logger.Error("Struct validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi struktur gagal", h.formatValidationError(err), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Put(ctx, "` + data.PutEndpoint + `", req, &rawResponse); err != nil { + h.logger.Error("Failed to update ` + data.Name + `", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal memperbarui ` + data.Name + `", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS update returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` updated successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + }) + + h.sendSuccessResponse(c, "` + data.Name + ` berhasil diperbarui", rawResponse.Response, requestID) }` } -func generateBpjsDeleteMethod(data BpjsHandlerData) string { - var routePath string +func generateOptimizedBpjsDeleteMethod(data BpjsHandlerData) string { + var routePath, tagName string if data.Category != "" { routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) } else { routePath = data.NameLower - } - - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower + tagName = strings.Title(data.NameLower) } return ` -// Delete` + data.Name + ` godoc +// Delete` + data.Name + ` deletes an existing ` + data.Name + ` with comprehensive validation // @Summary Delete an existing ` + data.NameUpper + ` -// @Description Delete a ` + data.Name + ` by ID +// @Description Delete a ` + data.Name + ` by ID with enhanced validation and logging // @Tags ` + tagName + ` // @Accept json // @Produce json // @Param id path string true "` + data.Name + ` ID" -// @Param user query string true "User" +// @Param user query string true "User identifier" // @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" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - missing parameters" +// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" +// @Failure 500 {object} models.` + data.Name + `Response "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 - } + requestID := uuid.New().String() + startTime := time.Now() + id := c.Param("id") + user := c.Query("user") - body := models.` + data.Name + `DeleteRequest{} - body.T` + data.Name + `.ID = id - body.T` + data.Name + `.User = user + h.logger.Info("Deleting ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + "id": id, + "user": user, + }) - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() + // Validate parameters + if id == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter ID wajib diisi", "", requestID) + return + } - if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", body); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed", "message": err.Error()}) - return - } + if user == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter user wajib diisi", "", requestID) + return + } - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "` + data.Name + ` berhasil dihapus", - Data: nil, - }) + req := models.` + data.Name + `DeleteRequest{ + BaseRequest: models.BaseRequest{ + RequestID: requestID, + Timestamp: startTime, + }, + T` + data.Name + `: models.` + data.Name + `DeleteData{ + ID: id, + User: user, + }, + } + + if err := req.Validate(); err != nil { + h.logger.Error("Delete validation failed", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", + "Validasi gagal", err.Error(), requestID) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + var rawResponse models.BpjsRawResponse + if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", req, &rawResponse); err != nil { + h.logger.Error("Failed to delete ` + data.Name + `", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "id": id, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal menghapus ` + data.Name + `", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + h.logger.Warn("BPJS delete returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + "id": id, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` deleted successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + "id": id, + }) + + h.sendSuccessResponse(c, "` + data.Name + ` berhasil dihapus", rawResponse.Response, requestID) }` } -func generateBpjsGetMethod(data BpjsHandlerData) string { - var routePath string +func generateOptimizedBpjsGetMethod(data BpjsHandlerData) string { + var routePath, tagName string if data.Category != "" { routePath = data.Category + "/" + data.NameLower + tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) } else { routePath = data.NameLower - } - - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower + tagName = strings.Title(data.NameLower) } return ` -// Get` + data.Name + ` godoc +// Get` + data.Name + ` retrieves ` + data.Name + ` details with comprehensive error handling // @Summary Get an existing ` + data.NameUpper + ` -// @Description Retrieve a ` + data.Name + ` by ID +// @Description Retrieve a ` + data.Name + ` by ID with enhanced validation and logging // @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" +// @Failure 400 {object} models.` + data.Name + `Response "Bad request - invalid ID" +// @Failure 404 {object} models.` + data.Name + `Response "` + data.Name + ` not found" +// @Failure 500 {object} models.` + data.Name + `Response "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 - } + requestID := uuid.New().String() + startTime := time.Now() + id := c.Param("id") - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() + h.logger.Info("Getting ` + data.Name + `", map[string]interface{}{ + "request_id": requestID, + "timestamp": startTime, + "id": id, + }) - 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 - } + if id == "" { + h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", + "Parameter ID wajib diisi", "", requestID) + return + } - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "Data ` + data.Name + ` berhasil diambil", - Data: result, - }) + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) + var rawResponse models.BpjsRawResponse + + if err := h.service.Get(ctx, endpoint, &rawResponse); err != nil { + h.logger.Error("Failed to get ` + data.Name + `", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + "id": id, + }) + + statusCode, errorCode := h.categorizeError(err) + h.sendErrorResponse(c, statusCode, errorCode, + "Gagal mengambil data ` + data.Name + `", err.Error(), requestID) + return + } + + if rawResponse.MetaData.Code != "200" { + // Handle specific BPJS error codes + if rawResponse.MetaData.Code == "201" { + h.logger.Info("` + data.Name + ` not found", map[string]interface{}{ + "request_id": requestID, + "id": id, + }) + h.sendErrorResponse(c, http.StatusNotFound, "DATA_NOT_FOUND", + "Data ` + data.Name + ` tidak ditemukan", rawResponse.MetaData.Message, requestID) + return + } + + h.logger.Warn("BPJS get returned error", map[string]interface{}{ + "bpjs_code": rawResponse.MetaData.Code, + "bpjs_message": rawResponse.MetaData.Message, + "request_id": requestID, + "id": id, + }) + + statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) + h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, + rawResponse.MetaData.Message, "", requestID) + return + } + + duration := time.Since(startTime) + h.logger.Info("` + data.Name + ` retrieved successfully", map[string]interface{}{ + "request_id": requestID, + "duration": duration.String(), + "id": id, + }) + + h.sendSuccessResponse(c, "Data ` + data.Name + ` berhasil diambil", rawResponse.Response, requestID) }` } -// ================= MODEL GENERATION ===================== +func generateHelperMethods(data BpjsHandlerData) string { + return ` -func generateBpjsModelFile(data BpjsHandlerData, modelDir string) { +// Helper methods for ` + data.Name + `Handler +func (h *` + data.Name + `Handler) sendSuccessResponse(c *gin.Context, message string, data interface{}, requestID string) { + response := models.` + data.Name + `Response{ + BaseResponse: models.BaseResponse{ + Status: "success", + Message: message, + Data: data, + Metadata: &models.ResponseMetadata{ + Timestamp: time.Now(), + Version: "2.0", + RequestID: requestID, + }, + }, + } + c.JSON(http.StatusOK, response) +} + +func (h *` + data.Name + `Handler) sendErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { + response := models.` + data.Name + `Response{ + BaseResponse: models.BaseResponse{ + Status: "error", + Message: message, + Error: &models.ErrorResponse{ + Code: errorCode, + Message: message, + Details: details, + }, + Metadata: &models.ResponseMetadata{ + Timestamp: time.Now(), + Version: "2.0", + RequestID: requestID, + }, + }, + } + c.JSON(statusCode, response) +} + +func (h *` + data.Name + `Handler) formatValidationError(err error) string { + if validationErrors, ok := err.(validator.ValidationErrors); ok { + var messages []string + for _, e := range validationErrors { + switch e.Tag() { + case "required": + messages = append(messages, fmt.Sprintf("%s wajib diisi", e.Field())) + case "min": + messages = append(messages, fmt.Sprintf("%s minimal %s karakter", e.Field(), e.Param())) + case "max": + messages = append(messages, fmt.Sprintf("%s maksimal %s karakter", e.Field(), e.Param())) + case "oneof": + messages = append(messages, fmt.Sprintf("%s harus salah satu dari: %s", e.Field(), e.Param())) + default: + messages = append(messages, fmt.Sprintf("%s tidak valid", e.Field())) + } + } + return fmt.Sprintf("Validasi gagal: %v", messages) + } + return err.Error() +} + +func (h *` + data.Name + `Handler) categorizeError(err error) (int, string) { + if err == nil { + return http.StatusOK, "SUCCESS" + } + + errStr := err.Error() + + if h.isTimeoutError(err) { + return http.StatusRequestTimeout, "REQUEST_TIMEOUT" + } + + if h.isNetworkError(err) { + return http.StatusBadGateway, "NETWORK_ERROR" + } + + if h.isAuthError(errStr) { + return http.StatusUnauthorized, "AUTH_ERROR" + } + + return http.StatusInternalServerError, "INTERNAL_ERROR" +} + +func (h *` + data.Name + `Handler) mapBpjsCodeToHttpStatus(bpjsCode string) int { + switch bpjsCode { + case "200": + return http.StatusOK + case "201": + return http.StatusNotFound + case "202": + return http.StatusBadRequest + case "400": + return http.StatusBadRequest + case "401": + return http.StatusUnauthorized + case "403": + return http.StatusForbidden + case "404": + return http.StatusNotFound + case "500": + return http.StatusInternalServerError + default: + return http.StatusUnprocessableEntity + } +} + +func (h *` + data.Name + `Handler) isTimeoutError(err error) bool { + return err != nil && (err.Error() == "context deadline exceeded" || + err.Error() == "timeout") +} + +func (h *` + data.Name + `Handler) isNetworkError(err error) bool { + return err != nil && (err.Error() == "connection refused" || + err.Error() == "no such host") +} + +func (h *` + data.Name + `Handler) isAuthError(errStr string) bool { + return errStr == "unauthorized" || errStr == "invalid credentials" +}` +} + +// ================= OPTIMIZED MODEL GENERATION ===================== + +func generateOptimizedBpjsModelFile(data BpjsHandlerData, modelDir string) { modelContent := `package models -// ` + data.Name + ` BPJS Models +import ( + "encoding/json" + "fmt" + "time" +) + +// ` + data.Name + ` BPJS Models with Enhanced Validation // 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\"`" + ` +// Base request/response structures +type BaseRequest struct { + RequestID string ` + "`json:\"request_id,omitempty\"`" + ` + Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` } -type ` + data.Name + `RawResponse struct { - MetaData struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - } ` + "`json:\"metaData\"`" + ` - Response interface{} ` + "`json:\"response\"`" + ` +type BaseResponse struct { + Status string ` + "`json:\"status\"`" + ` + Message string ` + "`json:\"message\"`" + ` + Data interface{} ` + "`json:\"data,omitempty\"`" + ` + Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + ` + Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + ` +} + +type ErrorResponse struct { + Code string ` + "`json:\"code\"`" + ` + Message string ` + "`json:\"message\"`" + ` + Details string ` + "`json:\"details,omitempty\"`" + ` +} + +type ResponseMetadata struct { + Timestamp time.Time ` + "`json:\"timestamp\"`" + ` + Version string ` + "`json:\"version\"`" + ` + RequestID string ` + "`json:\"request_id,omitempty\"`" + ` +} + +// ` + data.Name + ` Response Structure +type ` + data.Name + `Response struct { + BaseResponse +} + +// BPJS Raw Response Structure +type BpjsRawResponse 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 +// ` + data.Name + ` POST Request Structure with Enhanced Validation type ` + data.Name + `PostRequest struct { - T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` + BaseRequest + T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"t` + data.NameLower + `\" binding:\"required\" validate:\"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\"`" + ` + // Core BPJS fields - customize based on your specific requirements + NoKartu string ` + "`json:\"noKartu\" binding:\"required\" validate:\"required,min=13,max=13\"`" + ` + TglLayanan string ` + "`json:\"tglLayanan\" binding:\"required\" validate:\"required\"`" + ` + JnsPelayanan string ` + "`json:\"jnsPelayanan\" binding:\"required\" validate:\"required,oneof=1 2\"`" + ` + PpkPelayanan string ` + "`json:\"ppkPelayanan\" binding:\"required\" validate:\"required\"`" + ` + Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` + User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` +} + +// Validate validates the ` + data.Name + `PostRequest +func (r *` + data.Name + `PostRequest) Validate() error { + if r.T` + data.Name + `.NoKartu == "" { + return fmt.Errorf("nomor kartu tidak boleh kosong") + } + + if len(r.T` + data.Name + `.NoKartu) != 13 { + return fmt.Errorf("nomor kartu harus 13 digit") + } + + if _, err := time.Parse("2006-01-02", r.T` + data.Name + `.TglLayanan); err != nil { + return fmt.Errorf("format tanggal layanan tidak valid, gunakan yyyy-MM-dd") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *` + data.Name + `PostRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err }` } if data.HasPut { modelContent += ` -// ` + data.Name + ` PUT Request Structure +// ` + data.Name + ` PUT Request Structure with Enhanced Validation type ` + data.Name + `PutRequest struct { - T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` + BaseRequest + T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"t` + data.NameLower + `\" binding:\"required\" validate:\"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\"`" + ` + ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` + NoKartu string ` + "`json:\"noKartu\" validate:\"omitempty,min=13,max=13\"`" + ` + TglLayanan string ` + "`json:\"tglLayanan\" validate:\"omitempty\"`" + ` + JnsPelayanan string ` + "`json:\"jnsPelayanan\" validate:\"omitempty,oneof=1 2\"`" + ` + PpkPelayanan string ` + "`json:\"ppkPelayanan\" validate:\"omitempty\"`" + ` + Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` + User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` +} + +// Validate validates the ` + data.Name + `PutRequest +func (r *` + data.Name + `PutRequest) Validate() error { + if r.T` + data.Name + `.ID == "" { + return fmt.Errorf("ID tidak boleh kosong") + } + + if r.T` + data.Name + `.NoKartu != "" && len(r.T` + data.Name + `.NoKartu) != 13 { + return fmt.Errorf("nomor kartu harus 13 digit") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *` + data.Name + `PutRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err }` } if data.HasDelete { modelContent += ` -// ` + data.Name + ` DELETE Request Structure +// ` + data.Name + ` DELETE Request Structure with Enhanced Validation 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\"`" + ` + BaseRequest + T` + data.Name + ` ` + data.Name + `DeleteData ` + "`json:\"t` + data.NameLower + `\" binding:\"required\" validate:\"required\"`" + ` +} + +type ` + data.Name + `DeleteData struct { + ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` + User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` +} + +// Validate validates the ` + data.Name + `DeleteRequest +func (r *` + data.Name + `DeleteRequest) Validate() error { + if r.T` + data.Name + `.ID == "" { + return fmt.Errorf("ID tidak boleh kosong") + } + + if r.T` + data.Name + `.User == "" { + return fmt.Errorf("User tidak boleh kosong") + } + + return nil +} + +// ToJSON converts struct to JSON string +func (r *` + data.Name + `DeleteRequest) ToJSON() (string, error) { + data, err := json.Marshal(r) + return string(data), err }` } - // Add helper structures + // Add common helper structures modelContent += ` -// Common Helper Structures +// Common Helper Structures for BPJS type Flag struct { - Flag string ` + "`json:\"flag\" binding:\"required\"`" + ` + Flag string ` + "`json:\"flag\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` } type Poli struct { - Tujuan string ` + "`json:\"tujuan\"`" + ` - Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\"`" + ` + Tujuan string ` + "`json:\"tujuan\" binding:\"required\" validate:\"required\"`" + ` + Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` } -// Validation helpers +type KlsRawat struct { + KlsRawatHak string ` + "`json:\"klsRawatHak\" binding:\"required\" validate:\"required,oneof=1 2 3\"`" + ` + KlsRawatNaik string ` + "`json:\"klsRawatNaik\" validate:\"omitempty,oneof=1 2 3 4 5 6 7\"`" + ` + Pembiayaan string ` + "`json:\"pembiayaan\" validate:\"omitempty,oneof=1 2 3\"`" + ` + PenanggungJawab string ` + "`json:\"penanggungJawab\" validate:\"omitempty,max=100\"`" + ` +} + +// Validation helper functions func IsValidStatus(status string) bool { - validStatuses := []string{"active", "inactive", "pending", "processed"} - for _, v := range validStatuses { - if v == status { - return true - } - } - return false + validStatuses := []string{"active", "inactive", "pending", "processed"} + for _, v := range validStatuses { + if v == status { + return true + } + } + return false +} + +func IsValidJnsPelayanan(jns string) bool { + return jns == "1" || jns == "2" // 1: rawat jalan, 2: rawat inap +} + +func IsValidKlsRawat(kls string) bool { + validKelas := []string{"1", "2", "3"} + for _, v := range validKelas { + if v == kls { + return true + } + } + return false }` - writeFileBpjs(filepath.Join(modelDir, data.NameLower+".go"), modelContent) + writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) } -// ================= ROUTES GENERATION ===================== +// ================= OPTIMIZED ROUTES GENERATION ===================== -func updateBpjsRoutesFile(data BpjsHandlerData) { +func updateOptimizedBpjsRoutesFile(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) + fmt.Printf("📝 Please manually add these optimized routes to your routes.go file:\n") + printOptimizedBpjsRoutesSample(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) @@ -527,15 +1001,20 @@ func updateBpjsRoutesFile(data BpjsHandlerData) { } } - // Build routes - newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name) + // Build optimized routes + newRoutes := fmt.Sprintf("\t\t// Optimized %s endpoints with enhanced error handling\n", data.Name) if data.Category != "" { - newRoutes = fmt.Sprintf("\t\t// %s %s endpoints\n", data.Category, data.Name) + newRoutes = fmt.Sprintf("\t\t// Optimized %s %s endpoints with enhanced error handling\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) + newRoutes += fmt.Sprintf("\t\t%sHandlerConfig := &%s.%sHandlerConfig{\n", data.NameLower, importAlias, data.Name) + newRoutes += fmt.Sprintf("\t\t\tBpjsConfig: &config.LoadConfig().Bpjs,\n") + newRoutes += fmt.Sprintf("\t\t\tLogger: logger.GetLogger(),\n") + newRoutes += fmt.Sprintf("\t\t\tValidator: validator.New(),\n") + newRoutes += fmt.Sprintf("\t\t}\n") + newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler(%sHandlerConfig)\n", + data.NameLower, importAlias, data.Name, data.NameLower) + var routePath string if data.Category != "" { routePath = data.Category + "/" + data.NameLower @@ -572,7 +1051,7 @@ func updateBpjsRoutesFile(data BpjsHandlerData) { routesContent = strings.Replace(routesContent, insertMarker, newRoutes+insertMarker, 1) } else { - fmt.Printf("✅ Routes for %s already exist, skipping...\n", data.Name) + fmt.Printf("✅ Optimized routes for %s already exist, skipping...\n", data.Name) return } } @@ -583,13 +1062,13 @@ func updateBpjsRoutesFile(data BpjsHandlerData) { } if data.Category != "" { - fmt.Printf("✅ Updated routes.go with %s %s endpoints\n", data.Category, data.Name) + fmt.Printf("✅ Updated routes.go with optimized %s %s endpoints\n", data.Category, data.Name) } else { - fmt.Printf("✅ Updated routes.go with %s endpoints\n", data.Name) + fmt.Printf("✅ Updated routes.go with optimized %s endpoints\n", data.Name) } } -func printBpjsRoutesSample(data BpjsHandlerData) { +func printOptimizedBpjsRoutesSample(data BpjsHandlerData) { var importAlias string if data.Category != "" { importAlias = data.NameLower + "Handlers" @@ -606,14 +1085,24 @@ func printBpjsRoutesSample(data BpjsHandlerData) { if data.Category != "" { fmt.Printf(` -// %s %s endpoints -%sHandler := %s.New%sHandler(config.LoadConfig().Bpjs) -`, data.Category, data.Name, data.NameLower, importAlias, data.Name) +// Optimized %s %s endpoints with enhanced error handling +%sHandlerConfig := &%s.%sHandlerConfig{ + BpjsConfig: &config.LoadConfig().Bpjs, + Logger: logger.GetLogger(), + Validator: validator.New(), +} +%sHandler := %s.New%sHandler(%sHandlerConfig) +`, data.Category, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) } else { fmt.Printf(` -// %s endpoints -%sHandler := %s.New%sHandler(config.LoadConfig().Bpjs) -`, data.Name, data.NameLower, importAlias, data.Name) +// Optimized %s endpoints with enhanced error handling +%sHandlerConfig := &%s.%sHandlerConfig{ + BpjsConfig: &config.LoadConfig().Bpjs, + Logger: logger.GetLogger(), + Validator: validator.New(), +} +%sHandler := %s.New%sHandler(%sHandlerConfig) +`, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) } if data.HasGet { @@ -634,11 +1123,11 @@ func printBpjsRoutesSample(data BpjsHandlerData) { // ================= UTILITY FUNCTIONS ===================== -func writeFileBpjs(filename, content string) { +func writeFile(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) + fmt.Printf("✅ Generated optimized file: %s\n", filename) } diff --git a/tools/generate-bpjs-handler.go.backup b/tools/generate-bpjs-handler.go.backup new file mode 100644 index 0000000..e6c3c66 --- /dev/null +++ b/tools/generate-bpjs-handler.go.backup @@ -0,0 +1,644 @@ +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) +}