generete bpjs

This commit is contained in:
2025-08-29 05:21:24 +07:00
parent a6c9b12d3d
commit 0545bd2298
8 changed files with 854 additions and 1714 deletions

View File

@@ -87,6 +87,12 @@ go run tools/general/generate-handler.go orders/product get post
go run tools/general/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/bpjs/generate-bpjs-handler.go reference/peserta get go run tools/bpjs/generate-bpjs-handler.go reference/peserta get
# 1. Test generate satu service
go run tools/bpjs/generate-handler.go services-config-bpjs.yaml vclaim
# 2. Test generate semua service
go run tools/bpjs/generate-handler.go services-config-bpjs.yaml
``` ```
### Method Tersedia ### Method Tersedia

163
handler.tmpl Normal file
View File

@@ -0,0 +1,163 @@
// Code generated by genhandler; DO NOT EDIT.
package handlers
import (
"context"
"net/http"
"time"
"{{.ModuleName}}/internal/models/reference"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
/* ===== HANDLER STRUCT & CONSTRUCTOR ===== */
type {{.ServiceName}}Handler struct {
svc reference.{{.ServiceName}}Service
timeout time.Duration
}
func New{{.ServiceName}}Handler(svc reference.{{.ServiceName}}Service) *{{.ServiceName}}Handler {
return &{{.ServiceName}}Handler{
svc: svc,
timeout: time.Duration({{.Timeout}}) * time.Second,
}
}
/* ===== ENDPOINTS ===== */
{{range .Endpoints}}
{{/* ---------- GET ---------- */}}
{{if .HasGet}}
{{if $.HasSwagger}}
// @Summary {{.Summary}}
// @Description {{.Description}}
// @Tags {{ join .Tags "," }}
// @Accept json
// @Produce json
{{range .PathParams}}
// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
// @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.GetPath}} [get]
{{end}}
func (h *{{$.ServiceName}}Handler) Get{{.NameCamel}}(c *gin.Context) {
// ... (isi fungsi handler)
}
{{end}}
{{/* ---------- POST ---------- */}}
{{if .HasPost}}
{{if $.HasSwagger}}
// @Summary {{.Summary}}
// @Description {{.Description}}
// @Tags {{ join .Tags "," }}
// @Accept json
// @Produce json
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
// @Param request body reference.{{.Model}} true "{{.Name}} data"
// @Success 201 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.PostPath}} [post]
{{end}}
func (h *{{$.ServiceName}}Handler) Create{{.NameCamel}}(c *gin.Context) {
// ... (isi fungsi handler)
}
{{end}}
{{/* ---------- PUT ---------- */}}
{{if .HasPut}}
{{if $.HasSwagger}}
// @Summary Update {{.Name}}
// @Description {{.Description}}
// @Tags {{ join .Tags "," }}
// @Accept json
// @Produce json
{{range .PathParams}}
// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
// @Param request body reference.{{.Model}} true "{{.Name}} data"
// @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.PutPath}} [put]
{{end}}
func (h *{{$.ServiceName}}Handler) Update{{.NameCamel}}(c *gin.Context) {
// ... (isi fungsi handler)
}
{{end}}
{{/* ---------- PATCH ---------- */}}
{{if .HasPatch}}
{{if $.HasSwagger}}
// @Summary Patch {{.Name}}
// @Description {{.Description}}
// @Tags {{ join .Tags "," }}
// @Accept json
// @Produce json
{{range .PathParams}}
// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
// @Param request body reference.{{.Model}} true "Partial {{.Name}} data"
// @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.PatchPath}} [patch]
{{end}}
func (h *{{$.ServiceName}}Handler) Patch{{.NameCamel}}(c *gin.Context) {
// ... (isi fungsi handler)
}
{{end}}
{{/* ---------- DELETE ---------- */}}
{{if .HasDelete}}
{{if $.HasSwagger}}
// @Summary Delete {{.Name}}
// @Description {{.Description}}
// @Tags {{ join .Tags "," }}
// @Accept json
// @Produce json
{{range .PathParams}}
// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
// @Success 204 {object} nil
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.DeletePath}} [delete]
{{end}}
func (h *{{$.ServiceName}}Handler) Delete{{.NameCamel}}(c *gin.Context) {
// ... (isi fungsi handler)
}
{{end}}
{{end}}
/* ===== HELPER FUNCTIONS ===== */
func getOrSetRequestID(c *gin.Context) string {
id := c.GetHeader("X-Request-ID")
if id == "" {
id = uuid.New().String()
c.Header("X-Request-ID", id)
}
return id
}
func errorJSON(c *gin.Context, code int, reqID string, err error) {
c.JSON(code, gin.H{
"status": "error",
"message": err.Error(),
"request_id": reqID,
})
}

View File

@@ -1,401 +1,256 @@
// Code generated by generate-dynamic-handler.go; DO NOT EDIT. // Code generated by generate-handler.go; DO NOT EDIT.
// Generated at: 2025-08-28 17:47:19 // Generated at: 2025-08-29 04:42:52
// Service: VClaim (vclaim) // Service: VClaim (vclaim)
// Description: BPJS VClaim service for eligibility and SEP management
package handlers package handlers
import ( import (
"context" "context"
"fmt" "net/http"
"net/http" "time"
"strconv" "api-service/internal/models/reference"
"strings" "github.com/gin-gonic/gin"
"time" "github.com/google/uuid"
"api-service/internal/config"
"api-service/internal/models/reference"
"api-service/internal/services/bpjs"
"api-service/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
) )
// VClaimHandler handles VClaim BPJS services type VClaimHandler struct {}
type VClaimHandler struct {
service services.VClaimService
validator *validator.Validate
logger logger.Logger
config config.BpjsConfig
}
// VClaimHandlerConfig contains configuration for VClaimHandler func NewVClaimHandler() *VClaimHandler {
type VClaimHandlerConfig struct { return &VClaimHandler{}
BpjsConfig config.BpjsConfig
Logger logger.Logger
Validator *validator.Validate
}
// NewVClaimHandler creates a new VClaimHandler
func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler {
return &VClaimHandler{
service: services.NewService(cfg.BpjsConfig),
validator: cfg.Validator,
logger: cfg.Logger,
config: cfg.BpjsConfig,
}
} }
// GetPESERTA retrieves Peserta data //
// @Summary Get Get Participant Info data
// @Summary Get Peserta data
// @Description Get participant eligibility information // @Description Get participant eligibility information
// @Tags vclaim,peserta // @Tags vclaim,peserta
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param nokartu path string true "nokartu"
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param nokartu path string true "nokartu"
// @Success 200 {object} reference.PesertaResponse // @Success 200 {object} reference.PesertaResponse
// @Failure 400 {object} reference.ErrorResponse // @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse
// @Router /Peserta/:nokartu [get] // @Router /Peserta/:nokartu [get]
func (h *VClaimHandler) GetGet Participant Info(c *gin.Context) {
func (h *VClaimHandler) GetPESERTA(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), time.Duration(30)*time.Second)
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel()
defer cancel() reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
// Generate request ID if not present reqID = uuid.New().String()
requestID := c.GetHeader("X-Request-ID") c.Header("X-Request-ID", reqID)
if requestID == "" { }
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID) nokartu := c.Param("nokartu")
}
// TODO: Panggil service layer, mapping response, error handling dsb.
c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": reqID})
h.logger.Info("Processing GetPeserta request", map[string]interface{}{
"request_id": requestID,
"endpoint": "/Peserta/:nokartu",
"nokartu": c.Param("nokartu"),
})
// Extract path parameters
nokartu := c.Param("nokartu")
if nokartu == "" {
h.logger.Error("Missing required parameter nokartu", map[string]interface{}{
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter nokartu",
RequestID: requestID,
})
return
}
// Call service method
var response reference.PesertaResponse
result, err := h.service.GetPESERTA(ctx, nokartu)
if err != nil {
h.logger.Error("Failed to get Peserta", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
} }
// GetSEP retrieves Sep data
// @Summary Get Sep data
//
// @Summary Get SEP Management data
// @Description Manage SEP (Surat Eligibilitas Peserta) // @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep // @Tags vclaim,sep
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param nosep path string true "nosep"
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param nosep path string true "nosep"
// @Success 200 {object} reference.SEPResponse // @Success 200 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse // @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse
// @Router /SEP/:nosep [get] // @Router /SEP/:nosep [get]
func (h *VClaimHandler) GetSEP Management(c *gin.Context) {
func (h *VClaimHandler) GetSEP(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), time.Duration(30)*time.Second)
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel()
defer cancel() reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
// Generate request ID if not present reqID = uuid.New().String()
requestID := c.GetHeader("X-Request-ID") c.Header("X-Request-ID", reqID)
if requestID == "" { }
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID) nosep := c.Param("nosep")
}
// TODO: Panggil service layer, mapping response, error handling dsb.
c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": reqID})
h.logger.Info("Processing GetSep request", map[string]interface{}{
"request_id": requestID,
"endpoint": "/SEP/:nosep",
"nosep": c.Param("nosep"),
})
// Extract path parameters
nosep := c.Param("nosep")
if nosep == "" {
h.logger.Error("Missing required parameter nosep", map[string]interface{}{
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter nosep",
RequestID: requestID,
})
return
}
// Call service method
var response reference.SEPResponse
result, err := h.service.GetSEP(ctx, nosep)
if err != nil {
h.logger.Error("Failed to get Sep", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
} }
//
// CreateSEP creates new Sep // @Summary Create SEP Management data
// @Summary Create Sep
// @Description Manage SEP (Surat Eligibilitas Peserta) // @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep // @Tags vclaim,sep
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param request body reference.SEPRequest true "Sep data" // @Param request body reference.SEPRequest true "SEP Management data"
// @Success 201 {object} reference.SEPResponse // @Success 201 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse // @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse
// @Router /sep [post] // @Router /sep [post]
func (h *VClaimHandler) CreateSEP Management(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), time.Duration(30)*time.Second)
defer cancel()
reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
reqID = uuid.New().String()
c.Header("X-Request-ID", reqID)
}
var req reference.SEPRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": err.Error(), "request_id": reqID})
return
}
// TODO: Panggil service layer, validasi, error handling dsb.
c.JSON(http.StatusCreated, gin.H{"status": "success", "request_id": reqID})
}
func (h *VClaimHandler) CreateSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID") //
if requestID == "" { // @Summary Update SEP Management data
requestID = uuid.New().String() // @Description Update Manage SEP (Surat Eligibilitas Peserta)
c.Header("X-Request-ID", requestID) // @Tags vclaim,sep
} // @Accept json
// @Produce json
// @Param nosep path string true "nosep"
// @Security ApiKeyAuth
h.logger.Info("Processing CreateSep request", map[string]interface{}{
"request_id": requestID,
})
var req reference.SEPRequest // @Param request body reference.SEPRequest true "SEP Management data"
if err := c.ShouldBindJSON(&req); err != nil { // @Success 200 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse
h.logger.Error("Invalid request body", map[string]interface{}{ // @Failure 500 {object} reference.ErrorResponse
"error": err.Error(), // @Router /sep/:nosep [put]
"request_id": requestID, func (h *VClaimHandler) UpdateSEP Management(c *gin.Context) {
}) ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
c.JSON(http.StatusBadRequest, reference.ErrorResponse{ requestID := c.GetHeader("X-Request-ID")
Status: "error", if requestID == "" {
Message: "Invalid request body: " + err.Error(), requestID = uuid.New().String()
RequestID: requestID, c.Header("X-Request-ID", requestID)
}) }
return
}
// Validate request
if err := h.validator.Struct(&req); err != nil { nosep := c.Param("nosep")
if nosep == "" {
h.logger.Error("Validation failed", map[string]interface{}{ c.JSON(http.StatusBadRequest, reference.ErrorResponse{
"error": err.Error(), Status: "error",
"request_id": requestID, Message: "Missing required parameter nosep",
}) RequestID: requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{ return
Status: "error", }
Message: "Validation failed: " + err.Error(),
RequestID: requestID,
})
return
}
// Call service method var req reference.SEPRequest
var response reference.SEPResponse if err := c.ShouldBindJSON(&req); err != nil {
result, err := h.service.CreateSEP(ctx, &req) c.JSON(http.StatusBadRequest, reference.ErrorResponse{
if err != nil { Status: "error",
Message: "Invalid request body: " + err.Error(),
h.logger.Error("Failed to create Sep", map[string]interface{}{ RequestID: requestID,
"error": err.Error(), })
"request_id": requestID, return
}) }
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields // TODO: Validasi, panggil service update
response.Status = "success" // result, err := h.service.UpdateSEP Management(ctx, nosep, &req)
response.RequestID = requestID // if err != nil {
c.JSON(http.StatusCreated, response) // c.JSON(http.StatusInternalServerError, ...)
// return
// }
c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": requestID})
} }
// GetRUJUKAN retrieves Rujukan data //
// @Summary Delete SEP Management data
// @Description Delete Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Param nosep path string true "nosep"
// @Summary Get Rujukan data // @Security ApiKeyAuth
// @Success 204 {object} nil
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /sep/:nosep [delete]
func (h *VClaimHandler) DeleteSEP Management(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
nosep := c.Param("nosep")
if nosep == "" {
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter nosep",
RequestID: requestID,
})
return
}
// TODO: Panggil service delete
// err := h.service.DeleteSEP Management(ctx, nosep, )
// if err != nil {
// c.JSON(http.StatusInternalServerError, ...)
// return
// }
c.Status(http.StatusNoContent)
}
//
// @Summary Get Get Referral Info data
// @Description Get referral information // @Description Get referral information
// @Tags vclaim,rujukan // @Tags vclaim,rujukan
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param norujukan path string true "norujukan"
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param norujukan path string true "norujukan"
// @Success 200 {object} reference.RujukanResponse // @Success 200 {object} reference.RujukanResponse
// @Failure 400 {object} reference.ErrorResponse // @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse
// @Router /rujukan/:norujukan [get] // @Router /rujukan/:norujukan [get]
func (h *VClaimHandler) GetGet Referral Info(c *gin.Context) {
func (h *VClaimHandler) GetRUJUKAN(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), time.Duration(30)*time.Second)
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel()
defer cancel() reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
// Generate request ID if not present reqID = uuid.New().String()
requestID := c.GetHeader("X-Request-ID") c.Header("X-Request-ID", reqID)
if requestID == "" { }
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID) norujukan := c.Param("norujukan")
}
// TODO: Panggil service layer, mapping response, error handling dsb.
c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": reqID})
h.logger.Info("Processing GetRujukan request", map[string]interface{}{
"request_id": requestID,
"endpoint": "/rujukan/:norujukan",
"norujukan": c.Param("norujukan"),
})
// Extract path parameters
norujukan := c.Param("norujukan")
if norujukan == "" {
h.logger.Error("Missing required parameter norujukan", map[string]interface{}{
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter norujukan",
RequestID: requestID,
})
return
}
// Call service method
var response reference.RujukanResponse
result, err := h.service.GetRUJUKAN(ctx, norujukan)
if err != nil {
h.logger.Error("Failed to get Rujukan", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
} }

View File

@@ -67,83 +67,4 @@ services:
cache_enabled: true cache_enabled: true
cache_ttl: 600 cache_ttl: 600
eclaim:
name: "EClaim"
category: "eclaim"
package: "eclaim"
description: "BPJS EClaim service for claim processing and grouper"
base_url: "https://apijkn.bpjs-kesehatan.go.id/new-eclaim-rest"
timeout: 60
retry_count: 2
middleware:
- "RequestLogger"
- "ResponseLogger"
- "ClaimValidator"
dependencies:
- "database"
- "grouper_service"
endpoints:
klaim:
methods: ["GET", "POST", "PUT"]
get_path: "/klaim/:noklaim"
post_path: "/klaim"
put_path: "/klaim/:noklaim"
model: "KlaimRequest"
response_model: "KlaimResponse"
description: "Manage insurance claims"
summary: "Claim Management"
tags: ["eclaim", "klaim"]
require_auth: true
rate_limit: 30
cache_enabled: false
grouper:
methods: ["POST"]
post_path: "/grouper"
model: "GrouperRequest"
response_model: "GrouperResponse"
description: "Process claim grouping and pricing"
summary: "Claim Grouper"
tags: ["eclaim", "grouper"]
require_auth: true
rate_limit: 20
cache_enabled: true
cache_ttl: 120
aplicare:
name: "Aplicare"
category: "aplicare"
package: "aplicare"
description: "BPJS Aplicare service for reference data and monitoring"
base_url: "https://apijkn.bpjs-kesehatan.go.id/aplicaresws"
timeout: 45
retry_count: 3
middleware:
- "RequestLogger"
- "ResponseLogger"
dependencies:
- "database"
endpoints:
referensi:
methods: ["GET"]
get_path: "/referensi/:jenis"
model: "ReferensiRequest"
response_model: "ReferensiResponse"
description: "Get reference data (diagnoses, procedures, etc.)"
summary: "Get Reference Data"
tags: ["aplicare", "referensi"]
require_auth: true
rate_limit: 200
cache_enabled: true
cache_ttl: 3600
monitoring:
methods: ["GET", "POST"]
get_path: "/monitoring/:tanggal"
post_path: "/monitoring"
model: "MonitoringRequest"
response_model: "MonitoringResponse"
description: "Healthcare monitoring and reporting"
summary: "Monitoring Data"
tags: ["aplicare", "monitoring"]
require_auth: true
rate_limit: 50
cache_enabled: false

149
test Normal file
View File

@@ -0,0 +1,149 @@
# services-config-complete.yaml
global:
module_name: "api-service"
output_dir: "internal/handlers"
package_prefix: "api-service"
enable_swagger: true
enable_logging: true
enable_metrics: true
services:
vclaim:
name: "VClaim"
category: "vclaim"
package: "vclaim"
description: "BPJS VClaim service for eligibility and SEP management"
base_url: "https://apijkn.bpjs-kesehatan.go.id/vclaim-rest"
timeout: 30
retry_count: 3
middleware:
- "RequestLogger"
- "ResponseLogger"
- "RateLimiter"
dependencies:
- "database"
- "redis"
endpoints:
peserta:
methods: ["GET"]
get_path: "/Peserta/:nokartu"
model: "PesertaRequest"
response_model: "PesertaResponse"
description: "Get participant eligibility information"
summary: "Get Participant Info"
tags: ["vclaim", "peserta"]
require_auth: true
rate_limit: 100
cache_enabled: true
cache_ttl: 300
sep:
methods: ["GET", "POST", "PUT", "DELETE"]
get_path: "/SEP/:nosep"
post_path: "/sep"
put_path: "/sep/:nosep"
delete_path: "/sep/:nosep"
model: "SEPRequest"
response_model: "SEPResponse"
description: "Manage SEP (Surat Eligibilitas Peserta)"
summary: "SEP Management"
tags: ["vclaim", "sep"]
require_auth: true
rate_limit: 50
cache_enabled: true
cache_ttl: 180
custom_headers:
X-Service: "VClaim"
X-Version: "2.0"
rujukan:
methods: ["GET"]
get_path: "/rujukan/:norujukan"
model: "RujukanRequest"
response_model: "RujukanResponse"
description: "Get referral information"
summary: "Get Referral Info"
tags: ["vclaim", "rujukan"]
require_auth: true
rate_limit: 100
cache_enabled: true
cache_ttl: 600
eclaim:
name: "EClaim"
category: "eclaim"
package: "eclaim"
description: "BPJS EClaim service for claim processing and grouper"
base_url: "https://apijkn.bpjs-kesehatan.go.id/new-eclaim-rest"
timeout: 60
retry_count: 2
middleware:
- "RequestLogger"
- "ResponseLogger"
- "ClaimValidator"
dependencies:
- "database"
- "grouper_service"
endpoints:
klaim:
methods: ["GET", "POST", "PUT"]
get_path: "/klaim/:noklaim"
post_path: "/klaim"
put_path: "/klaim/:noklaim"
model: "KlaimRequest"
response_model: "KlaimResponse"
description: "Manage insurance claims"
summary: "Claim Management"
tags: ["eclaim", "klaim"]
require_auth: true
rate_limit: 30
cache_enabled: false
grouper:
methods: ["POST"]
post_path: "/grouper"
model: "GrouperRequest"
response_model: "GrouperResponse"
description: "Process claim grouping and pricing"
summary: "Claim Grouper"
tags: ["eclaim", "grouper"]
require_auth: true
rate_limit: 20
cache_enabled: true
cache_ttl: 120
aplicare:
name: "Aplicare"
category: "aplicare"
package: "aplicare"
description: "BPJS Aplicare service for reference data and monitoring"
base_url: "https://apijkn.bpjs-kesehatan.go.id/aplicaresws"
timeout: 45
retry_count: 3
middleware:
- "RequestLogger"
- "ResponseLogger"
dependencies:
- "database"
endpoints:
referensi:
methods: ["GET"]
get_path: "/referensi/:jenis"
model: "ReferensiRequest"
response_model: "ReferensiResponse"
description: "Get reference data (diagnoses, procedures, etc.)"
summary: "Get Reference Data"
tags: ["aplicare", "referensi"]
require_auth: true
rate_limit: 200
cache_enabled: true
cache_ttl: 3600
monitoring:
methods: ["GET", "POST"]
get_path: "/monitoring/:tanggal"
post_path: "/monitoring"
model: "MonitoringRequest"
response_model: "MonitoringResponse"
description: "Healthcare monitoring and reporting"
summary: "Monitoring Data"
tags: ["aplicare", "monitoring"]
require_auth: true
rate_limit: 50
cache_enabled: false

View File

@@ -1,24 +1,25 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"text/template" "text/template"
"time" "time"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// ServiceConfig represents the main configuration structure /* ---------- Model Konfig ---------- */
type ServiceConfig struct { type ServiceConfig struct {
Services map[string]Service `yaml:"services"` Services map[string]Service `yaml:"services"`
Global GlobalConfig `yaml:"global,omitempty"` Global GlobalConfig `yaml:"global,omitempty"`
} }
// GlobalConfig contains global configuration
type GlobalConfig struct { type GlobalConfig struct {
ModuleName string `yaml:"module_name"` ModuleName string `yaml:"module_name"`
OutputDir string `yaml:"output_dir"` OutputDir string `yaml:"output_dir"`
@@ -28,7 +29,6 @@ type GlobalConfig struct {
EnableMetrics bool `yaml:"enable_metrics"` EnableMetrics bool `yaml:"enable_metrics"`
} }
// Service represents individual service configuration
type Service struct { type Service struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Category string `yaml:"category"` Category string `yaml:"category"`
@@ -38,11 +38,10 @@ type Service struct {
Timeout int `yaml:"timeout"` Timeout int `yaml:"timeout"`
RetryCount int `yaml:"retry_count"` RetryCount int `yaml:"retry_count"`
Endpoints map[string]Endpoint `yaml:"endpoints"` Endpoints map[string]Endpoint `yaml:"endpoints"`
Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string Middleware []string `yaml:"middleware,omitempty"`
Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string Dependencies []string `yaml:"dependencies,omitempty"`
} }
// Endpoint represents endpoint configuration
type Endpoint struct { type Endpoint struct {
Methods []string `yaml:"methods"` Methods []string `yaml:"methods"`
GetPath string `yaml:"get_path,omitempty"` GetPath string `yaml:"get_path,omitempty"`
@@ -59,10 +58,11 @@ type Endpoint struct {
RateLimit int `yaml:"rate_limit,omitempty"` RateLimit int `yaml:"rate_limit,omitempty"`
CacheEnabled bool `yaml:"cache_enabled"` CacheEnabled bool `yaml:"cache_enabled"`
CacheTTL int `yaml:"cache_ttl,omitempty"` CacheTTL int `yaml:"cache_ttl,omitempty"`
CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` // ADDED: Missing field CustomHeaders map[string]string `yaml:"custom_headers,omitempty"`
} }
// TemplateData holds data for generating handlers /* ---------- Data Template ---------- */
type TemplateData struct { type TemplateData struct {
ServiceName string ServiceName string
ServiceLower string ServiceLower string
@@ -82,342 +82,338 @@ type TemplateData struct {
HasSwagger bool HasSwagger bool
HasAuth bool HasAuth bool
HasCache bool HasCache bool
Dependencies []string // FIXED: Changed to []string Dependencies []string
Middleware []string // FIXED: Changed to []string Middleware []string
GlobalConfig GlobalConfig GlobalConfig GlobalConfig
} }
// EndpointData represents processed endpoint data
type EndpointData struct { type EndpointData struct {
Name string Name string
NameLower string NameLower string
NameUpper string NameUpper string
NameCamel string NameCamel string
Methods []string Methods []string
GetPath string GetPath string
PostPath string PostPath string
PutPath string PutPath string
DeletePath string DeletePath string
PatchPath string PatchPath string
Model string Model string
ResponseModel string ResponseModel string
Description string Description string
Summary string Summary string
Tags []string Tags []string
HasGet bool HasGet bool
HasPost bool HasPost bool
HasPut bool HasPut bool
HasDelete bool HasDelete bool
HasPatch bool HasPatch bool
RequireAuth bool RequireAuth bool
RateLimit int RateLimit int
CacheEnabled bool CacheEnabled bool
CacheTTL int CacheTTL int
PathParams []string PathParams []string
QueryParams []string CustomHeaders map[string]string
RequiredFields []string
OptionalFields []string
CustomHeaders map[string]string
} }
// Template remains the same as before... /* ---------- Template Handler ---------- */
const handlerTemplate = ` const handlerTemplate = `
// Code generated by generate-dynamic-handler.go; DO NOT EDIT. // Code generated by generate-handler.go; DO NOT EDIT.
// Generated at: {{.Timestamp}} // Generated at: {{.Timestamp}}
// Service: {{.ServiceName}} ({{.Category}}) // Service: {{.ServiceName}} ({{.Category}})
// Description: {{.Description}}
package handlers package handlers
import ( import (
"context" "context"
"fmt" "net/http"
"net/http" "time"
"strconv" "{{.ModuleName}}/internal/models/reference"
"strings" "github.com/gin-gonic/gin"
"time" "github.com/google/uuid"
"{{.ModuleName}}/internal/config"
"{{.ModuleName}}/internal/models/reference"
"{{.ModuleName}}/internal/services/bpjs"
"{{.ModuleName}}/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
) )
// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services type {{.ServiceName}}Handler struct {}
type {{.ServiceName}}Handler struct {
service services.{{.ServiceName}}Service
validator *validator.Validate
logger logger.Logger
config config.BpjsConfig
}
// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler func New{{.ServiceName}}Handler() *{{.ServiceName}}Handler {
type {{.ServiceName}}HandlerConfig struct { return &{{.ServiceName}}Handler{}
BpjsConfig config.BpjsConfig
Logger logger.Logger
Validator *validator.Validate
}
// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler
func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler {
return &{{.ServiceName}}Handler{
service: services.NewService(cfg.BpjsConfig),
validator: cfg.Validator,
logger: cfg.Logger,
config: cfg.BpjsConfig,
}
} }
{{range .Endpoints}} {{range .Endpoints}}
{{if .HasGet}} {{if .HasGet}}
// Get{{.NameUpper}} retrieves {{.Name}} data //
{{if $.HasSwagger}}
// @Summary Get {{.Name}} data // @Summary Get {{.Name}} data
// @Description {{.Description}} // @Description {{.Description}}
// @Tags {{join .Tags ","}} // @Tags {{join .Tags ","}}
// @Accept json // @Accept json
// @Produce json // @Produce json
{{if .RequireAuth}} {{range .PathParams}}// @Param {{.}} path string true "{{.}}"
// @Security ApiKeyAuth
{{end}} {{end}}
{{range .PathParams}} {{if .RequireAuth}}// @Security ApiKeyAuth
// @Param {{.}} path string true "{{.}}"
{{end}} {{end}}
// @Success 200 {object} reference.{{.ResponseModel}} // @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse // @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse
// @Router {{.GetPath}} [get] // @Router {{.GetPath}} [get]
{{end}} func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) {
func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), time.Duration({{$.Timeout}})*time.Second)
ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel()
defer cancel() reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
// Generate request ID if not present reqID = uuid.New().String()
requestID := c.GetHeader("X-Request-ID") c.Header("X-Request-ID", reqID)
if requestID == "" { }
requestID = uuid.New().String() {{range .PathParams}}
c.Header("X-Request-ID", requestID) {{.}} := c.Param("{{.}}")
} {{end}}
// TODO: Panggil service layer, mapping response, error handling dsb.
{{if $.HasLogger}} c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": reqID})
h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{
"request_id": requestID,
"endpoint": "{{.GetPath}}",
{{range .PathParams}}
"{{.}}": c.Param("{{.}}"),
{{end}}
})
{{end}}
// Extract path parameters
{{range .PathParams}}
{{.}} := c.Param("{{.}}")
if {{.}} == "" {
{{if $.HasLogger}}
h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{
"request_id": requestID,
})
{{end}}
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter {{.}}",
RequestID: requestID,
})
return
}
{{end}}
// Call service method
var response reference.{{.ResponseModel}}
{{if .PathParams}}
result, err := h.service.Get{{.NameUpper}}(ctx{{range .PathParams}}, {{.}}{{end}})
{{else}}
result, err := h.service.Get{{.NameUpper}}(ctx)
{{end}}
if err != nil {
{{if $.HasLogger}}
h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
{{end}}
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
} }
{{end}} {{end}}
{{if .HasPost}} {{if .HasPost}}
// Create{{.NameUpper}} creates new {{.Name}} //
{{if $.HasSwagger}} // @Summary Create {{.Name}} data
// @Summary Create {{.Name}}
// @Description {{.Description}} // @Description {{.Description}}
// @Tags {{join .Tags ","}} // @Tags {{join .Tags ","}}
// @Accept json // @Accept json
// @Produce json // @Produce json
{{if .RequireAuth}} {{if .RequireAuth}}// @Security ApiKeyAuth
// @Security ApiKeyAuth
{{end}} {{end}}
// @Param request body reference.{{.Model}} true "{{.Name}} data" // @Param request body reference.{{.Model}} true "{{.Name}} data"
// @Success 201 {object} reference.{{.ResponseModel}} // @Success 201 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse // @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse
// @Router {{.PostPath}} [post] // @Router {{.PostPath}} [post]
func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), time.Duration({{$.Timeout}})*time.Second)
defer cancel()
reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
reqID = uuid.New().String()
c.Header("X-Request-ID", reqID)
}
var req reference.{{.Model}}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": err.Error(), "request_id": reqID})
return
}
// TODO: Panggil service layer, validasi, error handling dsb.
c.JSON(http.StatusCreated, gin.H{"status": "success", "request_id": reqID})
}
{{end}} {{end}}
func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) { {{if .HasPut}}
ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) //
defer cancel() // @Summary Update {{.Name}} data
// @Description Update {{.Description}}
// @Tags {{join .Tags ","}}
// @Accept json
// @Produce json
{{range .PathParams}}// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}// @Security ApiKeyAuth
{{end}}
// @Param request body reference.{{.Model}} true "{{.Name}} data"
// @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.PutPath}} [put]
func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
requestID := c.GetHeader("X-Request-ID") {{range .PathParams}}
if requestID == "" { {{.}} := c.Param("{{.}}")
requestID = uuid.New().String() if {{.}} == "" {
c.Header("X-Request-ID", requestID) c.JSON(http.StatusBadRequest, reference.ErrorResponse{
} Status: "error",
Message: "Missing required parameter {{.}}",
RequestID: requestID,
})
return
}
{{end}}
{{if $.HasLogger}} var req reference.{{.Model}}
h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ if err := c.ShouldBindJSON(&req); err != nil {
"request_id": requestID, c.JSON(http.StatusBadRequest, reference.ErrorResponse{
}) Status: "error",
{{end}} Message: "Invalid request body: " + err.Error(),
RequestID: requestID,
})
return
}
var req reference.{{.Model}} // TODO: Validasi, panggil service update
if err := c.ShouldBindJSON(&req); err != nil { // result, err := h.service.Update{{.Name}}(ctx, {{range .PathParams}}{{.}}, {{end}}&req)
{{if $.HasLogger}} // if err != nil {
h.logger.Error("Invalid request body", map[string]interface{}{ // c.JSON(http.StatusInternalServerError, ...)
"error": err.Error(), // return
"request_id": requestID, // }
}) c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": requestID})
{{end}} }
c.JSON(http.StatusBadRequest, reference.ErrorResponse{ {{end}}
Status: "error", {{if .HasPatch}}
Message: "Invalid request body: " + err.Error(), //
RequestID: requestID, // @Summary Patch {{.Name}} data
}) // @Description Patch {{.Description}}
return // @Tags {{join .Tags ","}}
} // @Accept json
// @Produce json
{{range .PathParams}}// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}// @Security ApiKeyAuth
{{end}}
// @Param request body reference.{{.Model}} true "Partial {{.Name}} data"
// @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.PatchPath}} [patch]
func (h *{{$.ServiceName}}Handler) Patch{{.Name}}(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Validate request {{range .PathParams}}
if err := h.validator.Struct(&req); err != nil { {{.}} := c.Param("{{.}}")
{{if $.HasLogger}} if {{.}} == "" {
h.logger.Error("Validation failed", map[string]interface{}{ c.JSON(http.StatusBadRequest, reference.ErrorResponse{
"error": err.Error(), Status: "error",
"request_id": requestID, Message: "Missing required parameter {{.}}",
}) RequestID: requestID,
{{end}} })
c.JSON(http.StatusBadRequest, reference.ErrorResponse{ return
Status: "error", }
Message: "Validation failed: " + err.Error(), {{end}}
RequestID: requestID,
})
return
}
// Call service method var req reference.{{.Model}}
var response reference.{{.ResponseModel}} if err := c.ShouldBindJSON(&req); err != nil {
result, err := h.service.Create{{.NameUpper}}(ctx, &req) c.JSON(http.StatusBadRequest, reference.ErrorResponse{
if err != nil { Status: "error",
{{if $.HasLogger}} Message: "Invalid request body: " + err.Error(),
h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ RequestID: requestID,
"error": err.Error(), })
"request_id": requestID, return
}) }
{{end}}
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields // TODO: Validasi, panggil service patch
response.Status = "success" // result, err := h.service.Patch{{.Name}}(ctx, {{range .PathParams}}{{.}}, {{end}}&req)
response.RequestID = requestID // if err != nil {
c.JSON(http.StatusCreated, response) // c.JSON(http.StatusInternalServerError, ...)
// return
// }
c.JSON(http.StatusOK, gin.H{"status": "success", "request_id": requestID})
}
{{end}}
{{if .HasDelete}}
//
// @Summary Delete {{.Name}} data
// @Description Delete {{.Description}}
// @Tags {{join .Tags ","}}
// @Accept json
// @Produce json
{{range .PathParams}}// @Param {{.}} path string true "{{.}}"
{{end}}
{{if .RequireAuth}}// @Security ApiKeyAuth
{{end}}
// @Success 204 {object} nil
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.DeletePath}} [delete]
func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
{{range .PathParams}}
{{.}} := c.Param("{{.}}")
if {{.}} == "" {
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter {{.}}",
RequestID: requestID,
})
return
}
{{end}}
// TODO: Panggil service delete
// err := h.service.Delete{{.Name}}(ctx, {{range .PathParams}}{{.}}, {{end}})
// if err != nil {
// c.JSON(http.StatusInternalServerError, ...)
// return
// }
c.Status(http.StatusNoContent)
} }
{{end}} {{end}}
{{end}} {{end}}
` `
/* ---------- Main ---------- */
func main() { func main() {
if len(os.Args) < 2 { flag.Usage = usage
printUsage() outFlag := flag.String("o", "", "override output directory")
serviceFlag := flag.String("s", "", "generate only selected service name")
tmplFlag := flag.String("tmpl", "", "custom handler template path")
flag.Parse()
if flag.NArg() < 1 {
usage()
os.Exit(1) os.Exit(1)
} }
configFile := os.Args[1] configFile := flag.Arg(0)
var targetService string
if len(os.Args) > 2 {
targetService = os.Args[2]
}
// Load configuration
config, err := loadConfig(configFile) config, err := loadConfig(configFile)
if err != nil { if err != nil {
fmt.Printf("❌ Error loading config: %v\n", err) fmt.Printf("❌ Error loading config: %v\n", err)
os.Exit(1) os.Exit(1)
} }
fmt.Println("🚀 Starting BPJS Dynamic Handler Generation...") outDir := firstNonEmpty(*outFlag, config.Global.OutputDir, "internal/handlers")
fmt.Printf("📁 Config file: %s\n", configFile) templatePath := firstNonEmpty(*tmplFlag, "")
if targetService != "" {
fmt.Printf("🎯 Target service: %s\n", targetService) // Template setup
var tmpl *template.Template
if templatePath != "" {
tmpl, err = template.New("handler").Funcs(templateFuncMap()).ParseFiles(templatePath)
} else {
tmpl, err = template.New("handler").Funcs(templateFuncMap()).Parse(handlerTemplate)
}
if err != nil {
fmt.Printf("❌ Error parsing template: %v\n", err)
os.Exit(1)
} }
generated := 0 var wg sync.WaitGroup
errors := 0 for svcName, svc := range config.Services {
if *serviceFlag != "" && svcName != *serviceFlag {
// Generate handlers
for serviceName, service := range config.Services {
if targetService != "" && serviceName != targetService {
continue continue
} }
wg.Add(1)
fmt.Printf("🔧 Generating handler for service: %s (%s)\n", service.Name, service.Category) go func(serviceName string, service Service) {
err := generateHandler(serviceName, service, config.Global) defer wg.Done()
if err != nil { err := generateHandler(serviceName, service, config.Global, outDir, tmpl)
fmt.Printf("❌ Error generating handler for %s: %v\n", serviceName, err) if err != nil {
errors++ fmt.Printf("❌ Error generating handler for %s: %v\n", serviceName, err)
continue } else {
} fmt.Printf("✅ Generated handler: %s_handler.go\n", strings.ToLower(serviceName))
fmt.Printf("✅ Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) }
generated++ }(svcName, svc)
} }
wg.Wait()
// Summary
fmt.Println("\n📊 Generation Summary:")
fmt.Printf(" ✅ Successfully generated: %d handlers\n", generated)
if errors > 0 {
fmt.Printf(" ❌ Failed: %d handlers\n", errors)
}
if generated > 0 {
fmt.Println("🎉 Generation completed successfully!")
}
}
func printUsage() {
fmt.Println("BPJS Dynamic Handler Generator")
fmt.Println()
fmt.Println("Usage:")
fmt.Println(" go run generate-dynamic-handler.go <config-file> [service-name]")
fmt.Println()
fmt.Println("Examples:")
fmt.Println(" go run generate-dynamic-handler.go services-config.yaml")
fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim")
} }
func loadConfig(filename string) (*ServiceConfig, error) { func loadConfig(filename string) (*ServiceConfig, error) {
@@ -431,140 +427,104 @@ func loadConfig(filename string) (*ServiceConfig, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse YAML config: %w", err) return nil, fmt.Errorf("failed to parse YAML config: %w", err)
} }
// Set default values
if config.Global.ModuleName == "" { if config.Global.ModuleName == "" {
config.Global.ModuleName = "api-service" config.Global.ModuleName = "api-service"
} }
if config.Global.OutputDir == "" { if config.Global.OutputDir == "" {
config.Global.OutputDir = "internal/handlers" config.Global.OutputDir = "internal/handlers"
} }
return &config, nil return &config, nil
} }
func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { func generateHandler(serviceName string, service Service, globalConfig GlobalConfig, outDir string, tmpl *template.Template) error {
// Prepare template data td := prepareTemplateData(serviceName, service, globalConfig)
templateData := TemplateData{ if err := os.MkdirAll(outDir, 0o755); err != nil {
ServiceName: service.Name, return fmt.Errorf("failed to create output directory: %w", err)
ServiceLower: strings.ToLower(service.Name), }
ServiceUpper: strings.ToUpper(service.Name), filename := filepath.Join(outDir, fmt.Sprintf("%s_handler.go", td.ServiceLower))
Category: service.Category, file, err := os.Create(filename)
Package: service.Package, if err != nil {
Description: service.Description, return fmt.Errorf("failed to create handler file: %w", err)
BaseURL: service.BaseURL, }
Timeout: getOrDefault(service.Timeout, 30), defer file.Close()
RetryCount: getOrDefault(service.RetryCount, 3), return tmpl.Execute(file, td)
}
func prepareTemplateData(serviceName string, svc Service, globalConfig GlobalConfig) TemplateData {
td := TemplateData{
ServiceName: svc.Name,
ServiceLower: strings.ToLower(svc.Name),
ServiceUpper: strings.ToUpper(svc.Name),
Category: svc.Category,
Package: svc.Package,
Description: svc.Description,
BaseURL: svc.BaseURL,
Timeout: getOrDefault(svc.Timeout, 30),
RetryCount: getOrDefault(svc.RetryCount, 3),
Timestamp: time.Now().Format("2006-01-02 15:04:05"), Timestamp: time.Now().Format("2006-01-02 15:04:05"),
ModuleName: globalConfig.ModuleName, ModuleName: globalConfig.ModuleName,
HasValidator: true, HasValidator: true,
HasLogger: globalConfig.EnableLogging, HasLogger: globalConfig.EnableLogging,
HasMetrics: globalConfig.EnableMetrics, HasMetrics: globalConfig.EnableMetrics,
HasSwagger: globalConfig.EnableSwagger, HasSwagger: globalConfig.EnableSwagger,
Dependencies: service.Dependencies, // Now []string Dependencies: svc.Dependencies,
Middleware: service.Middleware, // Now []string Middleware: svc.Middleware,
GlobalConfig: globalConfig, GlobalConfig: globalConfig,
} }
for _, ep := range svc.Endpoints {
// Check for advanced features ed := processEndpoint(ep)
for _, endpoint := range service.Endpoints { if ed.RequireAuth {
if endpoint.RequireAuth { td.HasAuth = true
templateData.HasAuth = true
} }
if endpoint.CacheEnabled { if ed.CacheEnabled {
templateData.HasCache = true td.HasCache = true
} }
td.Endpoints = append(td.Endpoints, ed)
} }
return td
// Process endpoints
for endpointName, endpoint := range service.Endpoints {
endpointData := processEndpoint(endpointName, endpoint)
templateData.Endpoints = append(templateData.Endpoints, endpointData)
}
// Create output directory
outputDir := globalConfig.OutputDir
if outputDir == "" {
outputDir = "internal/handlers"
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Generate handler file
filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName)))
// Create template with custom functions
tmpl := template.New("handler").Funcs(template.FuncMap{
"contains": strings.Contains,
"join": strings.Join,
"title": strings.Title,
"trimPrefix": strings.TrimPrefix,
})
tmpl, err := tmpl.Parse(handlerTemplate)
if err != nil {
return fmt.Errorf("failed to parse template: %w", err)
}
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer file.Close()
err = tmpl.Execute(file, templateData)
if err != nil {
return fmt.Errorf("failed to execute template: %w", err)
}
return nil
} }
func processEndpoint(name string, endpoint Endpoint) EndpointData { func processEndpoint(ep Endpoint) EndpointData {
data := EndpointData{ data := EndpointData{
Name: strings.Title(name), Name: strings.Title(ep.Summary),
NameLower: strings.ToLower(name), NameLower: strings.ToLower(ep.Summary),
NameUpper: strings.ToUpper(name), NameUpper: strings.ToUpper(ep.Summary),
NameCamel: toCamelCase(name), NameCamel: toCamelCase(ep.Summary),
Methods: endpoint.Methods, Methods: ep.Methods,
GetPath: endpoint.GetPath, GetPath: ep.GetPath,
PostPath: endpoint.PostPath, PostPath: ep.PostPath,
PutPath: endpoint.PutPath, PutPath: ep.PutPath,
DeletePath: endpoint.DeletePath, DeletePath: ep.DeletePath,
PatchPath: endpoint.PatchPath, PatchPath: ep.PatchPath,
Model: endpoint.Model, Model: ep.Model,
ResponseModel: endpoint.ResponseModel, ResponseModel: ep.ResponseModel,
Description: endpoint.Description, Description: ep.Description,
Summary: endpoint.Summary, Summary: ep.Summary,
Tags: endpoint.Tags, Tags: ep.Tags,
RequireAuth: endpoint.RequireAuth, RequireAuth: ep.RequireAuth,
RateLimit: endpoint.RateLimit, RateLimit: ep.RateLimit,
CacheEnabled: endpoint.CacheEnabled, CacheEnabled: ep.CacheEnabled,
CacheTTL: getOrDefault(endpoint.CacheTTL, 300), CacheTTL: getOrDefault(ep.CacheTTL, 300),
CustomHeaders: endpoint.CustomHeaders, CustomHeaders: ep.CustomHeaders,
} }
for _, method := range ep.Methods {
// Set method flags and extract path parameters
for _, method := range endpoint.Methods {
switch strings.ToUpper(method) { switch strings.ToUpper(method) {
case "GET": case "GET":
data.HasGet = true data.HasGet = true
data.PathParams = extractPathParams(endpoint.GetPath) data.PathParams = extractPathParams(ep.GetPath)
case "POST": case "POST":
data.HasPost = true data.HasPost = true
case "PUT": case "PUT":
data.HasPut = true data.HasPut = true
data.PathParams = extractPathParams(endpoint.PutPath) data.PathParams = extractPathParams(ep.PutPath)
case "DELETE": case "DELETE":
data.HasDelete = true data.HasDelete = true
data.PathParams = extractPathParams(endpoint.DeletePath) data.PathParams = extractPathParams(ep.DeletePath)
case "PATCH": case "PATCH":
data.HasPatch = true data.HasPatch = true
data.PathParams = extractPathParams(endpoint.PatchPath) data.PathParams = extractPathParams(ep.PatchPath)
} }
} }
return data return data
} }
@@ -572,7 +532,6 @@ func extractPathParams(path string) []string {
if path == "" { if path == "" {
return nil return nil
} }
var params []string var params []string
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
for _, part := range parts { for _, part := range parts {
@@ -580,7 +539,6 @@ func extractPathParams(path string) []string {
params = append(params, strings.TrimPrefix(part, ":")) params = append(params, strings.TrimPrefix(part, ":"))
} }
} }
return params return params
} }
@@ -588,16 +546,13 @@ func toCamelCase(str string) string {
words := strings.FieldsFunc(str, func(c rune) bool { words := strings.FieldsFunc(str, func(c rune) bool {
return c == '_' || c == '-' || c == ' ' return c == '_' || c == '-' || c == ' '
}) })
if len(words) == 0 { if len(words) == 0 {
return str return str
} }
result := strings.Title(strings.ToLower(words[0]))
result := strings.ToLower(words[0])
for _, word := range words[1:] { for _, word := range words[1:] {
result += strings.Title(strings.ToLower(word)) result += strings.Title(strings.ToLower(word))
} }
return result return result
} }
@@ -607,3 +562,32 @@ func getOrDefault(value, defaultValue int) int {
} }
return value return value
} }
func firstNonEmpty(vals ...string) string {
for _, v := range vals {
if v != "" {
return v
}
}
return ""
}
func templateFuncMap() template.FuncMap {
return template.FuncMap{
"contains": strings.Contains,
"join": strings.Join,
"title": strings.Title,
"trimPrefix": strings.TrimPrefix,
}
}
func usage() {
fmt.Println("BPJS Dynamic Handler Generator")
fmt.Println(`Usage:
go run generate-handler.go [flags] <yaml-config-file>
Flags:
-o Output directory
-s Single service name
-tmpl Custom handler template path
`)
}

View File

@@ -1,573 +0,0 @@
// Code generated by generate-dynamic-handler.go; DO NOT EDIT.
// Generated at: 2025-08-28 14:59:09
// Service: VClaim (vclaim)
// Description: BPJS VClaim service for eligibility and SEP management
package handlers
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"api-service/internal/config"
"api-service/internal/models/reference"
services "api-service/internal/services/bpjs"
"api-service/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
// VClaimHandler handles VClaim BPJS services
type VClaimHandler struct {
service services.VClaimService
validator *validator.Validate
logger logger.Logger
config *config.BpjsConfig
}
// VClaimHandlerConfig contains configuration for VClaimHandler
type VClaimHandlerConfig struct {
BpjsConfig *config.BpjsConfig
Logger logger.Logger
Validator *validator.Validate
}
// NewVClaimHandler creates a new VClaimHandler
func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler {
return &VClaimHandler{
service: services.NewService(*cfg.BpjsConfig),
validator: cfg.Validator,
logger: cfg.Logger,
config: cfg.BpjsConfig,
}
}
// GetPESERTA retrieves Peserta data
// @Summary Get Peserta data
// @Description Get participant eligibility information
// @Tags vclaim,peserta
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param nokartu path string true "Nokartu"
// @Success 200 {object} reference.PesertaResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /Peserta/:nokartu [get]
func (h *VClaimHandler) GetPESERTA(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Authentication check
if err := h.authenticateRequest(c); err != nil {
h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID)
c.JSON(http.StatusUnauthorized, reference.ErrorResponse{
Status: "error",
Message: "Authentication failed",
RequestID: requestID,
})
return
}
// Extract path parameters
nokartu := c.Param("nokartu")
if nokartu == "" {
h.logger.Error("Missing required parameter: nokartu", "request_id", requestID)
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter: nokartu",
RequestID: requestID,
})
return
}
// Check cache first
cacheKey := fmt.Sprintf("vclaim:peserta:%s", nokartu)
if cached, found := h.getCachedResponse(cacheKey); found {
h.logger.Info("Cache hit for GetPESERTA", "request_id", requestID, "cache_key", cacheKey)
c.Header("X-Cache", "HIT")
c.JSON(http.StatusOK, cached)
return
}
h.logger.Info("Processing GetPESERTA request",
"request_id", requestID,
"endpoint", "/Peserta/:nokartu",
"nokartu", nokartu)
// Call service method
result, err := h.service.GetPESERTA(ctx, nokartu)
if err != nil {
h.logger.Error("Failed to get Peserta",
"error", err.Error(),
"request_id", requestID)
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Prepare response
response := reference.PesertaResponse{
Status: "success",
Data: result,
RequestID: requestID,
}
// Cache successful response
h.setCachedResponse(cacheKey, response, 300)
c.Header("X-Cache", "MISS")
c.JSON(http.StatusOK, response)
}
// GetSEP retrieves Sep data
// @Summary Get Sep data
// @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param nosep path string true "Nosep"
// @Success 200 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /SEP/:nosep [get]
func (h *VClaimHandler) GetSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Authentication check
if err := h.authenticateRequest(c); err != nil {
h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID)
c.JSON(http.StatusUnauthorized, reference.ErrorResponse{
Status: "error",
Message: "Authentication failed",
RequestID: requestID,
})
return
}
// Extract path parameters
nosep := c.Param("nosep")
if nosep == "" {
h.logger.Error("Missing required parameter: nosep", "request_id", requestID)
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter: nosep",
RequestID: requestID,
})
return
}
// Check cache first
cacheKey := fmt.Sprintf("vclaim:sep:%s", nosep)
if cached, found := h.getCachedResponse(cacheKey); found {
h.logger.Info("Cache hit for GetSEP", "request_id", requestID, "cache_key", cacheKey)
c.Header("X-Cache", "HIT")
c.JSON(http.StatusOK, cached)
return
}
h.logger.Info("Processing GetSEP request",
"request_id", requestID,
"endpoint", "/SEP/:nosep",
"nosep", nosep)
// Call service method
result, err := h.service.GetSEP(ctx, nosep)
if err != nil {
h.logger.Error("Failed to get Sep",
"error", err.Error(),
"request_id", requestID)
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Prepare response
response := reference.SEPResponse{
Status: "success",
Data: result,
RequestID: requestID,
}
// Cache successful response
h.setCachedResponse(cacheKey, response, 180)
c.Header("X-Cache", "MISS")
c.JSON(http.StatusOK, response)
}
// CreateSEP creates new Sep
// @Summary Create Sep
// @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body reference.SEPRequest true "Sep data"
// @Success 201 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /sep [post]
func (h *VClaimHandler) CreateSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Authentication check
if err := h.authenticateRequest(c); err != nil {
h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID)
c.JSON(http.StatusUnauthorized, reference.ErrorResponse{
Status: "error",
Message: "Authentication failed",
RequestID: requestID,
})
return
}
var req reference.SEPRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Invalid request body",
"error", err.Error(),
"request_id", requestID)
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Invalid request body: " + err.Error(),
RequestID: requestID,
})
return
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
h.logger.Error("Validation failed",
"error", err.Error(),
"request_id", requestID)
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Validation failed: " + err.Error(),
RequestID: requestID,
})
return
}
h.logger.Info("Processing CreateSEP request", "request_id", requestID)
// Call service method
result, err := h.service.CreateSEP(ctx, &req)
if err != nil {
h.logger.Error("Failed to create Sep",
"error", err.Error(),
"request_id", requestID)
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
response := reference.SEPResponse{
Status: "success",
Data: result,
RequestID: requestID,
}
c.JSON(http.StatusCreated, response)
}
// UpdateSEP updates existing Sep
// @Summary Update Sep
// @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param nosep path string true "Nosep"
// @Param request body reference.SEPRequest true "Sep data"
// @Success 200 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /sep/:nosep [put]
func (h *VClaimHandler) UpdateSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Extract path parameters
nosep := c.Param("nosep")
if nosep == "" {
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter: nosep",
RequestID: requestID,
})
return
}
var req reference.SEPRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Invalid request body: " + err.Error(),
RequestID: requestID,
})
return
}
if err := h.validator.Struct(&req); err != nil {
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Validation failed: " + err.Error(),
RequestID: requestID,
})
return
}
// Invalidate cache
cacheKey := fmt.Sprintf("vclaim:sep:%s", nosep)
h.invalidateCache(cacheKey)
// Call service method
result, err := h.service.UpdateSEP(ctx, nosep, &req)
if err != nil {
h.logger.Error("Failed to update Sep", "error", err.Error(), "request_id", requestID)
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
response := reference.SEPResponse{
Status: "success",
Data: result,
RequestID: requestID,
}
c.JSON(http.StatusOK, response)
}
// DeleteSEP deletes existing Sep
// @Summary Delete Sep
// @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param nosep path string true "Nosep"
// @Success 200 {object} reference.BaseResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /sep/:nosep [delete]
func (h *VClaimHandler) DeleteSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Extract path parameters
nosep := c.Param("nosep")
if nosep == "" {
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter: nosep",
RequestID: requestID,
})
return
}
// Invalidate cache
cacheKey := fmt.Sprintf("vclaim:sep:%s", nosep)
h.invalidateCache(cacheKey)
// Call service method
err := h.service.DeleteSEP(ctx, nosep)
if err != nil {
h.logger.Error("Failed to delete Sep", "error", err.Error(), "request_id", requestID)
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
response := reference.BaseResponse{
Status: "success",
Message: "Sep deleted successfully",
RequestID: requestID,
}
c.JSON(http.StatusOK, response)
}
// GetRUJUKAN retrieves Rujukan data
// @Summary Get Rujukan data
// @Description Get referral information
// @Tags vclaim,rujukan
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param norujukan path string true "Norujukan"
// @Success 200 {object} reference.RujukanResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /rujukan/:norujukan [get]
func (h *VClaimHandler) GetRUJUKAN(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Authentication check
if err := h.authenticateRequest(c); err != nil {
h.logger.Error("Authentication failed", "error", err.Error(), "request_id", requestID)
c.JSON(http.StatusUnauthorized, reference.ErrorResponse{
Status: "error",
Message: "Authentication failed",
RequestID: requestID,
})
return
}
// Extract path parameters
norujukan := c.Param("norujukan")
if norujukan == "" {
h.logger.Error("Missing required parameter: norujukan", "request_id", requestID)
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter: norujukan",
RequestID: requestID,
})
return
}
// Check cache first
cacheKey := fmt.Sprintf("vclaim:rujukan:%s", norujukan)
if cached, found := h.getCachedResponse(cacheKey); found {
h.logger.Info("Cache hit for GetRUJUKAN", "request_id", requestID, "cache_key", cacheKey)
c.Header("X-Cache", "HIT")
c.JSON(http.StatusOK, cached)
return
}
h.logger.Info("Processing GetRUJUKAN request",
"request_id", requestID,
"endpoint", "/rujukan/:norujukan",
"norujukan", norujukan)
// Call service method
result, err := h.service.GetRUJUKAN(ctx, norujukan)
if err != nil {
h.logger.Error("Failed to get Rujukan",
"error", err.Error(),
"request_id", requestID)
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Prepare response
response := reference.RujukanResponse{
Status: "success",
Data: result,
RequestID: requestID,
}
// Cache successful response
h.setCachedResponse(cacheKey, response, 600)
c.Header("X-Cache", "MISS")
c.JSON(http.StatusOK, response)
}
// RegisterRoutes registers all VClaim routes
func (h *VClaimHandler) RegisterRoutes(router *gin.RouterGroup) {
router.GET("/Peserta/:nokartu", h.GetPESERTA)
router.GET("/SEP/:nosep", h.GetSEP)
router.POST("/sep", h.CreateSEP)
router.PUT("/sep/:nosep", h.UpdateSEP)
router.DELETE("/sep/:nosep", h.DeleteSEP)
router.GET("/rujukan/:norujukan", h.GetRUJUKAN)
}
// Helper methods
func (h *VClaimHandler) authenticateRequest(c *gin.Context) error {
token := c.GetHeader("Authorization")
if token == "" {
return fmt.Errorf("missing authorization header")
}
if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ")
}
return h.auth.ValidateToken(token)
}
func (h *VClaimHandler) getCachedResponse(key string) (interface{}, bool) {
if h.cache == nil {
return nil, false
}
return h.cache.Get(key)
}
func (h *VClaimHandler) setCachedResponse(key string, data interface{}, ttl int) {
if h.cache == nil {
return
}
h.cache.Set(key, data, time.Duration(ttl)*time.Second)
}
func (h *VClaimHandler) invalidateCache(key string) {
if h.cache == nil {
return
}
h.cache.Delete(key)
}

View File

@@ -1,365 +0,0 @@
// Code generated by generate-dynamic-handler.go; DO NOT EDIT.
// Generated at: 2025-08-28 14:47:36
// Service: VClaim (vclaim)
// Description: BPJS VClaim service for eligibility and SEP management
package handlers
import (
"api-service/internal/config"
"api-service/internal/models/reference"
services "api-service/internal/services/bpjs"
"api-service/pkg/logger"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
// VClaimHandler handles VClaim BPJS services
type VClaimHandler struct {
service services.VClaimService
validator *validator.Validate
logger logger.Logger
config config.BpjsConfig
}
// VClaimHandlerConfig contains configuration for VClaimHandler
type VClaimHandlerConfig struct {
BpjsConfig config.BpjsConfig
Logger logger.Logger
Validator *validator.Validate
}
// NewVClaimHandler creates a new VClaimHandler
func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler {
return &VClaimHandler{
service: services.NewService(cfg.BpjsConfig),
validator: cfg.Validator,
logger: cfg.Logger,
config: cfg.BpjsConfig,
}
}
// GetPESERTA retrieves Peserta data
// @Summary Get Peserta data
// @Description Get participant eligibility information
// @Tags vclaim,peserta
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param nokartu path string true "nokartu"
// @Success 200 {object} reference.PesertaResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /Peserta/:nokartu [get]
func (h *VClaimHandler) GetPeserta(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
h.logger.Info("Processing GetPeserta request", map[string]interface{}{
"request_id": requestID,
"endpoint": "/Peserta/:nokartu",
"nokartu": c.Param("nokartu"),
})
// Extract path parameters
nokartu := c.Param("nokartu")
if nokartu == "" {
h.logger.Error("Missing required parameter nokartu", map[string]interface{}{
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter nokartu",
RequestID: requestID,
})
return
}
// Call service method
var response reference.PesertaResponse
response, err := h.service.GetPeserta(ctx, nokartu)
if err != nil {
h.logger.Error("Failed to get Peserta", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
}
// GetSEP retrieves Sep data
// @Summary Get Sep data
// @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param nosep path string true "nosep"
// @Success 200 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /SEP/:nosep [get]
func (h *VClaimHandler) GetSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
h.logger.Info("Processing GetSep request", map[string]interface{}{
"request_id": requestID,
"endpoint": "/SEP/:nosep",
"nosep": c.Param("nosep"),
})
// Extract path parameters
nosep := c.Param("nosep")
if nosep == "" {
h.logger.Error("Missing required parameter nosep", map[string]interface{}{
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter nosep",
RequestID: requestID,
})
return
}
// Call service method
var response reference.SEPResponse
result, err := h.service.GetSEP(ctx, nosep)
if err != nil {
h.logger.Error("Failed to get Sep", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
}
// CreateSEP creates new Sep
// @Summary Create Sep
// @Description Manage SEP (Surat Eligibilitas Peserta)
// @Tags vclaim,sep
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body reference.SEPRequest true "Sep data"
// @Success 201 {object} reference.SEPResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /sep [post]
func (h *VClaimHandler) CreateSEP(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
h.logger.Info("Processing CreateSep request", map[string]interface{}{
"request_id": requestID,
})
var req reference.SEPRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Invalid request body", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Invalid request body: " + err.Error(),
RequestID: requestID,
})
return
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
h.logger.Error("Validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Validation failed: " + err.Error(),
RequestID: requestID,
})
return
}
// Call service method
var response reference.SEPResponse
response, err := h.service.CreateSEP(ctx, &req)
if err != nil {
h.logger.Error("Failed to create Sep", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusCreated, response)
}
// GetRUJUKAN retrieves Rujukan data
// @Summary Get Rujukan data
// @Description Get referral information
// @Tags vclaim,rujukan
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param norujukan path string true "norujukan"
// @Success 200 {object} reference.RujukanResponse
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router /rujukan/:norujukan [get]
func (h *VClaimHandler) GetRUJUKAN(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
h.logger.Info("Processing GetRujukan request", map[string]interface{}{
"request_id": requestID,
"endpoint": "/rujukan/:norujukan",
"norujukan": c.Param("norujukan"),
})
// Extract path parameters
norujukan := c.Param("norujukan")
if norujukan == "" {
h.logger.Error("Missing required parameter norujukan", map[string]interface{}{
"request_id": requestID,
})
c.JSON(http.StatusBadRequest, reference.ErrorResponse{
Status: "error",
Message: "Missing required parameter norujukan",
RequestID: requestID,
})
return
}
// Call service method
var response reference.RujukanResponse
response, err := h.service.GetRUJUKAN(ctx, norujukan)
if err != nil {
h.logger.Error("Failed to get Rujukan", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
Status: "error",
Message: "Internal server error",
RequestID: requestID,
})
return
}
// Ensure response has proper fields
response.Status = "success"
response.RequestID = requestID
c.JSON(http.StatusOK, response)
}