606 lines
19 KiB
Go
606 lines
19 KiB
Go
// Package peserta handles Peserta BPJS services
|
||
// Generated on: 2025-09-07 11:01:18
|
||
package handlers
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"reflect"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"api-service/internal/config"
|
||
"api-service/internal/database"
|
||
"api-service/internal/models"
|
||
"api-service/internal/models/vclaim/peserta"
|
||
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
|
||
type PesertaHandler struct {
|
||
service services.VClaimService
|
||
db database.Service
|
||
validator *validator.Validate
|
||
logger logger.Logger
|
||
config config.BpjsConfig
|
||
}
|
||
|
||
// PesertaHandlerConfig contains configuration for PesertaHandler
|
||
type PesertaHandlerConfig struct {
|
||
Config *config.Config
|
||
Logger logger.Logger
|
||
Validator *validator.Validate
|
||
}
|
||
|
||
// NewPesertaHandler creates a new PesertaHandler
|
||
func NewPesertaHandler(cfg PesertaHandlerConfig) *PesertaHandler {
|
||
return &PesertaHandler{
|
||
db: database.New(cfg.Config),
|
||
service: services.NewService(cfg.Config.Bpjs),
|
||
validator: cfg.Validator,
|
||
logger: cfg.Logger,
|
||
config: cfg.Config.Bpjs,
|
||
}
|
||
}
|
||
|
||
// min returns the minimum of two integers
|
||
func min(a, b int) int {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
|
||
// cleanResponse removes invalid characters and BOM from the response string
|
||
func cleanResponse(resp string) string {
|
||
// Remove UTF-8 BOM
|
||
// Konversi string ke byte slice untuk pengecekan BOM
|
||
data := []byte(resp)
|
||
// Cek dan hapus semua jenis representasi UTF-8 BOM
|
||
// 1. Byte sequence: EF BB BF
|
||
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
|
||
data = data[3:]
|
||
}
|
||
// 2. Unicode character: U+FEFF
|
||
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
|
||
data = data[3:]
|
||
}
|
||
// 3. Zero Width No-Break Space (Unicode)
|
||
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
|
||
data = data[3:]
|
||
}
|
||
// 4. Representasi heksadesimal lainnya
|
||
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
|
||
data = data[3:]
|
||
}
|
||
// Konversi kembali ke string
|
||
resp = string(data)
|
||
|
||
// Hapus karakter null
|
||
// Hapus semua karakter kontrol ASCII (0-31) kecuali whitespace yang valid
|
||
controlChars := []string{
|
||
"\x00", // Null character
|
||
"\x01", // Start of Heading
|
||
"\x02", // Start of Text
|
||
"\x03", // End of Text
|
||
"\x04", // End of Transmission (EOT)
|
||
"\x05", // Enquiry
|
||
"\x06", // Acknowledge
|
||
"\x07", // Bell
|
||
"\x08", // Backspace
|
||
"\x0B", // Vertical Tab
|
||
"\x0C", // Form Feed
|
||
"\x0E", // Shift Out
|
||
"\x0F", // Shift In
|
||
"\x10", // Data Link Escape
|
||
"\x11", // Device Control 1
|
||
"\x12", // Device Control 2
|
||
"\x13", // Device Control 3
|
||
"\x14", // Device Control 4
|
||
"\x15", // Negative Acknowledge
|
||
"\x16", // Synchronous Idle
|
||
"\x17", // End of Transmission Block
|
||
"\x18", // Cancel
|
||
"\x19", // End of Medium
|
||
"\x1A", // Substitute
|
||
"\x1B", // Escape
|
||
"\x1C", // File Separator
|
||
"\x1D", // Group Separator
|
||
"\x1E", // Record Separator
|
||
"\x1F", // Unit Separator
|
||
}
|
||
|
||
for _, char := range controlChars {
|
||
resp = strings.ReplaceAll(resp, char, "")
|
||
}
|
||
|
||
// Hapus karakter invalid termasuk backtick
|
||
invalidChars := []string{
|
||
"¢", // Cent sign
|
||
"\u00a2", // Cent sign Unicode
|
||
"\u0080", // Control character
|
||
"`", // Backtick
|
||
"´", // Acute accent
|
||
"‘", // Left single quote
|
||
"’", // Right single quote
|
||
"“", // Left double quote
|
||
"”", // Right double quote
|
||
}
|
||
|
||
for _, char := range invalidChars {
|
||
resp = strings.ReplaceAll(resp, char, "")
|
||
}
|
||
// Gunakan buffer pool untuk efisiensi memori
|
||
var bufPool = sync.Pool{
|
||
New: func() interface{} {
|
||
return &strings.Builder{}
|
||
},
|
||
}
|
||
buf := bufPool.Get().(*strings.Builder)
|
||
defer func() {
|
||
buf.Reset()
|
||
bufPool.Put(buf)
|
||
}()
|
||
|
||
// Definisikan karakter yang diperbolehkan
|
||
allowedChars := map[rune]bool{
|
||
'\n': true, '\r': true, '\t': true,
|
||
// Tambahkan karakter non-ASCII yang diperbolehkan jika adafalse
|
||
// Contoh:
|
||
// Latin-1 Supplement
|
||
// ASCII printable (32-126) kecuali backtick (96)
|
||
'!': true, '"': true, '#': true, '$': true, '%': true, '&': true,
|
||
'\'': true, '(': true, ')': true, '*': true, '+': true, ',': true,
|
||
'-': true, '.': true, '/': true, '0': true, '1': true, '2': true,
|
||
'3': true, '4': true, '5': true, '6': true, '7': true, '8': true,
|
||
'9': true, ':': true, ';': true, '<': true, '=': true, '>': true,
|
||
'?': true, '@': true, 'A': true, 'B': true, 'C': true, 'D': true,
|
||
'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true,
|
||
'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true,
|
||
'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true,
|
||
'W': true, 'X': true, 'Y': true, 'Z': true, '[': true, '\\': true,
|
||
']': true, '^': true, '_': true, 'a': true, 'b': true, 'c': true,
|
||
'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true,
|
||
'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true,
|
||
'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true,
|
||
'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '{': true,
|
||
'|': true, '}': true, '~': true,
|
||
|
||
// Latin-1 Supplement
|
||
'¡': true, '¢': true, '£': true, '¤': true, '¥': true, '¦': true,
|
||
'§': true, '¨': true, '©': true, 'ª': true, '«': true, '¬': true,
|
||
'®': true, '¯': true, '°': true, '±': true, '²': true, '³': true,
|
||
'´': true, 'µ': true, '¶': true, '·': true, '¸': true, '¹': true,
|
||
'º': true, '»': true, '¼': true, '½': true, '¾': true, '¿': true,
|
||
|
||
// Huruf Latin dengan diakritik (Lowercase)
|
||
'á': true, 'é': true, 'í': true, 'ó': true, 'ú': true, 'ý': true, 'þ': true,
|
||
'à': true, 'è': true, 'ì': true, 'ò': true, 'ù': true,
|
||
'â': true, 'ê': true, 'î': true, 'ô': true, 'û': true,
|
||
'ä': true, 'ë': true, 'ï': true, 'ö': true, 'ü': true, 'ÿ': true,
|
||
'ã': true, 'õ': true, 'ñ': true, 'ç': true,
|
||
'ā': true, 'ē': true, 'ī': true, 'ō': true, 'ū': true,
|
||
'ă': true, 'đ': true, 'ħ': true, 'ij': true, 'ĸ': true, 'ł': true,
|
||
'ŋ': true, 'œ': true, 'ŧ': true, 'ß': true,
|
||
|
||
// Huruf Latin dengan diakritik (Uppercase)
|
||
'Á': true, 'É': true, 'Í': true, 'Ó': true, 'Ú': true, 'Ý': true, 'Þ': true,
|
||
'À': true, 'È': true, 'Ì': true, 'Ò': true, 'Ù': true,
|
||
'Â': true, 'Ê': true, 'Î': true, 'Ô': true, 'Û': true,
|
||
'Ä': true, 'Ë': true, 'Ï': true, 'Ö': true, 'Ü': true,
|
||
'Ã': true, 'Õ': true, 'Ñ': true, 'Ç': true,
|
||
'Ā': true, 'Ē': true, 'Ī': true, 'Ō': true, 'Ū': true,
|
||
'Ă': true, 'Đ': true, 'Ħ': true, 'IJ': true, 'Ł': true,
|
||
'Ŋ': true, 'Œ': true, 'Ŧ': true, 'ẞ': true,
|
||
|
||
// Karakter Nordik dan lainnya
|
||
'Å': true, 'å': true, 'Æ': true, 'æ': true, 'Ø': true, 'ø': true,
|
||
'ſ': true, 'ʼn': true, 'ŀ': true,
|
||
|
||
// Tanda baca dan simbol matematika
|
||
'‐': true, '–': true, '—': true, '―': true, '‖': true, '‗': true,
|
||
'†': true, '‡': true, '•': true, '‣': true, '․': true, '‥': true,
|
||
'…': true, '‧': true, '‰': true, '′': true, '″': true, '‴': true,
|
||
'‵': true, '‶': true, '‷': true, '‸': true, '‹': true, '›': true,
|
||
'※': true,
|
||
|
||
// Simbol mata uang (hanya yang umum)
|
||
'€': true, '₹': true,
|
||
|
||
// Karakter lain yang mungkin diperlukan
|
||
}
|
||
|
||
// Filter karakter menggunakan buffer pool
|
||
for _, r := range resp {
|
||
if r < 128 || allowedChars[r] {
|
||
buf.WriteRune(r)
|
||
}
|
||
}
|
||
// Trim whitespace
|
||
result := strings.TrimSpace(buf.String())
|
||
return result
|
||
}
|
||
|
||
// extractCode extracts the code field from metaData using reflection
|
||
func extractCode(metaData interface{}) interface{} {
|
||
v := reflect.ValueOf(metaData)
|
||
switch v.Kind() {
|
||
case reflect.Struct:
|
||
codeField := v.FieldByName("Code")
|
||
if codeField.IsValid() {
|
||
return codeField.Interface()
|
||
}
|
||
case reflect.Map:
|
||
if m, ok := metaData.(map[string]interface{}); ok {
|
||
return m["code"]
|
||
}
|
||
case reflect.String:
|
||
var metaMap map[string]interface{}
|
||
if err := json.Unmarshal([]byte(metaData.(string)), &metaMap); err == nil {
|
||
return metaMap["code"]
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// parseHTTPStatusCode extracts HTTP status code from error message
|
||
func parseHTTPStatusCode(errMsg string) int {
|
||
if strings.Contains(errMsg, "HTTP error:") {
|
||
parts := strings.Split(errMsg, "HTTP error:")
|
||
if len(parts) > 1 {
|
||
statusPart := strings.TrimSpace(parts[1])
|
||
if statusCode, err := strconv.Atoi(strings.Fields(statusPart)[0]); err == nil {
|
||
return statusCode
|
||
}
|
||
}
|
||
}
|
||
return 500 // Default to internal server error
|
||
}
|
||
func (h *PesertaHandler) isValidJSON(str string) bool {
|
||
var js interface{}
|
||
return json.Unmarshal([]byte(str), &js) == nil
|
||
}
|
||
|
||
// GetBynik godoc
|
||
// @Summary Get Bynik data
|
||
// @Description Get participant eligibility information by NIK
|
||
// @Tags Peserta
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security ApiKeyAuth
|
||
// @Param X-Request-ID header string false "Request ID for tracking"
|
||
// @Param nik path string true "nik" example("example_value")
|
||
// @Success 200 {object} peserta.PesertaResponse "Successfully retrieved Bynik data"
|
||
// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters"
|
||
// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials"
|
||
// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Bynik not found"
|
||
// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error"
|
||
// @Router /Peserta/nik/:nik [get]
|
||
func (h *PesertaHandler) GetBynik(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)
|
||
}
|
||
|
||
// Get database connection
|
||
dbConn, err := h.db.GetDB("postgres_satudata")
|
||
if err != nil {
|
||
h.logger.Error("Database connection failed", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Database connection failed",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
// Note: dbConn is available for future database operations (e.g., caching, logging)
|
||
_ = dbConn // Prevent unused variable warning
|
||
|
||
// Context Paramaeter
|
||
now := time.Now()
|
||
dateStr := now.Format("2006-01-02")
|
||
fmt.Println("Date (YYYY-MM-DD):", dateStr)
|
||
h.logger.Info("Processing GetBynik request", map[string]interface{}{
|
||
"request_id": requestID,
|
||
"endpoint": "/Peserta/nik/:nik/tglSEP/" + dateStr,
|
||
"nik": c.Param("nik"),
|
||
})
|
||
|
||
// Extract path parameters
|
||
|
||
nik := c.Param("nik")
|
||
if nik == "" || nik == ":nik" {
|
||
|
||
h.logger.Error("Missing required parameter nik", map[string]interface{}{
|
||
"request_id": requestID,
|
||
})
|
||
|
||
c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Parameter NIK Masih Kosong / Isi Dahulu NIK!",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
var response peserta.PesertaResponse
|
||
|
||
endpoint := "/Peserta/nik/:nik/tglSEP/" + dateStr
|
||
|
||
endpoint = strings.Replace(endpoint, ":nik", nik, 1)
|
||
|
||
resp, err := h.service.GetRawResponse(ctx, endpoint)
|
||
|
||
if err != nil {
|
||
// Check if error message contains 404 status code
|
||
if strings.Contains(err.Error(), "HTTP error: 404") {
|
||
h.logger.Error("Bynik not found", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
|
||
c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Bynik not found",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
|
||
h.logger.Error("Failed to get Bynik", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
|
||
c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Internal server error",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
|
||
// Map the raw response
|
||
response.MetaData = resp.MetaData
|
||
if resp.Response != nil {
|
||
response.Data = &peserta.PesertaData{}
|
||
if respStr, ok := resp.Response.(string); ok {
|
||
// Decrypt the response string
|
||
consID, secretKey, _, tstamp, _ := h.config.SetHeader()
|
||
decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp)
|
||
if err != nil {
|
||
|
||
h.logger.Error("Failed to decrypt response", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
|
||
} else {
|
||
// Clean the decrypted response
|
||
cleanedResp := cleanResponse(decryptedResp)
|
||
if h.isValidJSON(cleanedResp) {
|
||
// Unmarshal kembali setelah dibersihkan
|
||
err = json.Unmarshal([]byte(cleanedResp), response.Data)
|
||
if err != nil {
|
||
h.logger.Warn("Failed to unmarshal decrypted response", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
"response_preview": cleanedResp[:min(100, len(cleanedResp))], // Log first 100 chars for debugging
|
||
})
|
||
// Set Data to nil if unmarshal fails to avoid sending empty struct
|
||
response.Data = nil
|
||
}
|
||
} else {
|
||
h.logger.Warn("Invalid JSON in data, storing as string", map[string]interface{}{
|
||
"request_id": requestID,
|
||
"response": cleanedResp,
|
||
})
|
||
response.Data.RawResponse = cleanedResp
|
||
}
|
||
|
||
}
|
||
} else if respMap, ok := resp.Response.(map[string]interface{}); ok {
|
||
// Response is already unmarshaled JSON
|
||
if dataMap, exists := respMap["peserta"]; exists {
|
||
dataBytes, _ := json.Marshal(dataMap)
|
||
json.Unmarshal(dataBytes, response.Data)
|
||
} else {
|
||
// Try to unmarshal the whole response
|
||
respBytes, _ := json.Marshal(resp.Response)
|
||
json.Unmarshal(respBytes, response.Data)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ensure response has proper fields
|
||
response.Status = "success"
|
||
response.RequestID = requestID
|
||
// Ambil status code dari metaData.code
|
||
var statusCode int
|
||
code := extractCode(response.MetaData)
|
||
if code != nil {
|
||
statusCode = models.GetStatusCodeFromMeta(code)
|
||
} else {
|
||
statusCode = 200
|
||
}
|
||
c.JSON(statusCode, response)
|
||
}
|
||
|
||
// GetBynokartu godoc
|
||
// @Summary Get Bynokartu data
|
||
// @Description Get participant eligibility information by card number
|
||
// @Tags Peserta
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security ApiKeyAuth
|
||
// @Param X-Request-ID header string false "Request ID for tracking"
|
||
// @Param nokartu path string true "nokartu" example("example_value")
|
||
// @Success 200 {object} peserta.PesertaResponse "Successfully retrieved Bynokartu data"
|
||
// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters"
|
||
// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials"
|
||
// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Bynokartu not found"
|
||
// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error"
|
||
// @Router /Peserta/nokartu/:nokartu [get]
|
||
func (h *PesertaHandler) GetBynokartu(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)
|
||
}
|
||
|
||
// Get database connection
|
||
dbConn, err := h.db.GetDB("postgres_satudata")
|
||
if err != nil {
|
||
h.logger.Error("Database connection failed", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Database connection failed",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
// Note: dbConn is available for future database operations (e.g., caching, logging)
|
||
_ = dbConn // Prevent unused variable warning
|
||
|
||
// Context Paramaeter
|
||
now := time.Now()
|
||
dateStr := now.Format("2006-01-02")
|
||
fmt.Println("Date (YYYY-MM-DD):", dateStr)
|
||
h.logger.Info("Processing GetBynokartu request", map[string]interface{}{
|
||
"request_id": requestID,
|
||
"endpoint": "/Peserta/nokartu/:nokartu/tglSEP/" + dateStr,
|
||
"nik": c.Param("nokartu"),
|
||
})
|
||
|
||
// Extract path parameters
|
||
|
||
nokartu := c.Param("nokartu")
|
||
if nokartu == "" || nokartu == ":nokartu" {
|
||
|
||
h.logger.Error("Missing required parameter nokartu", map[string]interface{}{
|
||
"request_id": requestID,
|
||
})
|
||
|
||
c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Parameter Nomor Kartu Bpjs Masih Kosong / Isi Dahulu Nomor Kartu!",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
var response peserta.PesertaResponse
|
||
|
||
endpoint := "/Peserta/nokartu/:nokartu/tglSEP/" + dateStr
|
||
|
||
endpoint = strings.Replace(endpoint, ":nokartu", nokartu, 1)
|
||
|
||
resp, err := h.service.GetRawResponse(ctx, endpoint)
|
||
|
||
if err != nil {
|
||
// Check if error message contains 404 status code
|
||
if strings.Contains(err.Error(), "HTTP error: 404") {
|
||
h.logger.Error("ByNoKartu not found", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
|
||
c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "ByNoKartu not found",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
|
||
h.logger.Error("Failed to get ByNoKartu", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
|
||
c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{
|
||
Status: "error",
|
||
Message: "Internal server error",
|
||
RequestID: requestID,
|
||
})
|
||
return
|
||
}
|
||
|
||
// Map the raw response
|
||
response.MetaData = resp.MetaData
|
||
if resp.Response != nil {
|
||
response.Data = &peserta.PesertaData{}
|
||
if respStr, ok := resp.Response.(string); ok {
|
||
// Decrypt the response string
|
||
consID, secretKey, _, tstamp, _ := h.config.SetHeader()
|
||
decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp)
|
||
if err != nil {
|
||
|
||
h.logger.Error("Failed to decrypt response", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
})
|
||
|
||
} else {
|
||
// Clean the decrypted response
|
||
cleanedResp := cleanResponse(decryptedResp)
|
||
err = json.Unmarshal([]byte(cleanedResp), response.Data)
|
||
if err != nil {
|
||
h.logger.Warn("Failed to unmarshal decrypted response", map[string]interface{}{
|
||
"error": err.Error(),
|
||
"request_id": requestID,
|
||
"response_preview": cleanedResp[:min(100, len(cleanedResp))], // Log first 100 chars for debugging
|
||
})
|
||
// Set Data to nil if unmarshal fails to avoid sending empty struct
|
||
response.Data = nil
|
||
}
|
||
}
|
||
} else if respMap, ok := resp.Response.(map[string]interface{}); ok {
|
||
// Response is already unmarshaled JSON
|
||
if dataMap, exists := respMap["peserta"]; exists {
|
||
dataBytes, _ := json.Marshal(dataMap)
|
||
json.Unmarshal(dataBytes, response.Data)
|
||
} else {
|
||
// Try to unmarshal the whole response
|
||
respBytes, _ := json.Marshal(resp.Response)
|
||
json.Unmarshal(respBytes, response.Data)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ensure response has proper fields
|
||
response.Status = "success"
|
||
response.RequestID = requestID
|
||
// Ambil status code dari metaData.code
|
||
var statusCode int
|
||
code := extractCode(response.MetaData)
|
||
if code != nil {
|
||
statusCode = models.GetStatusCodeFromMeta(code)
|
||
} else {
|
||
statusCode = 200
|
||
}
|
||
c.JSON(statusCode, response)
|
||
}
|