pembaruhan swagger dan tool

This commit is contained in:
2025-08-25 04:34:47 +07:00
parent 7681c796e8
commit 40af041bc9
8 changed files with 2332 additions and 238 deletions

View File

@@ -0,0 +1,533 @@
package handlers
import (
"context"
"fmt"
"net/http"
"time"
"api-service/internal/config"
models "api-service/internal/models/antrol"
services "api-service/internal/services/bpjs"
"api-service/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
// PesertaHandler handles peserta BPJS services with optimized error handling and logging
type PesertaHandler struct {
service services.VClaimService
validator *validator.Validate
logger logger.Logger
config *config.BpjsConfig
}
// HandlerConfig contains configuration for PesertaHandler
type PesertaHandlerConfig struct {
BpjsConfig *config.BpjsConfig
Logger logger.Logger
Validator *validator.Validate
}
// NewPesertaHandler creates a new optimized PesertaHandler
func NewPesertaHandler(cfg *PesertaHandlerConfig) *PesertaHandler {
return &PesertaHandler{
service: services.NewService(*cfg.BpjsConfig),
validator: cfg.Validator,
logger: cfg.Logger,
config: cfg.BpjsConfig,
}
}
// CreatePeserta creates a new Peserta with comprehensive error handling and validation
// @Summary Create a new PESERTA
// @Description Create a new Peserta in BPJS system with enhanced validation and logging
// @Tags Antrol-Peserta
// @Accept json
// @Produce json
// @Param request body models.PesertaPostRequest true "Peserta creation request"
// @Success 200 {object} models.PesertaResponse "Peserta created successfully"
// @Failure 400 {object} models.PesertaResponse "Bad request - validation error"
// @Failure 422 {object} models.PesertaResponse "Unprocessable entity - business logic error"
// @Failure 500 {object} models.PesertaResponse "Internal server error"
// @Router /api/v1/antrol/peserta [post]
func (h *PesertaHandler) CreatePeserta(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
h.logger.Info("Creating Peserta", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
})
var req models.PesertaPostRequest
req.RequestID = requestID
req.Timestamp = startTime
// Bind and validate JSON
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT",
"Format request tidak valid", err.Error(), requestID)
return
}
// Custom validation
if err := req.Validate(); err != nil {
h.logger.Error("Custom validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR",
"Validasi gagal", err.Error(), requestID)
return
}
// Struct validation
if err := h.validator.Struct(&req); err != nil {
h.logger.Error("Struct validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR",
"Validasi struktur gagal", h.formatValidationError(err), requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
var rawResponse models.BpjsRawResponse
if err := h.service.Post(ctx, "PESERTA/2.0/insert", req, &rawResponse); err != nil {
h.logger.Error("Failed to call BPJS service", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"endpoint": "PESERTA/2.0/insert",
})
statusCode, errorCode := h.categorizeError(err)
h.sendErrorResponse(c, statusCode, errorCode,
"Gagal membuat Peserta", err.Error(), requestID)
return
}
// Check BPJS response
if rawResponse.MetaData.Code != "200" {
h.logger.Warn("BPJS returned error", map[string]interface{}{
"bpjs_code": rawResponse.MetaData.Code,
"bpjs_message": rawResponse.MetaData.Message,
"request_id": requestID,
})
statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code)
h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code,
rawResponse.MetaData.Message, "", requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("Peserta created successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
})
h.sendSuccessResponse(c, "Peserta berhasil dibuat", rawResponse.Response, requestID)
}
// UpdatePeserta updates an existing Peserta with comprehensive validation
// @Summary Update an existing PESERTA
// @Description Update an existing Peserta in BPJS system with enhanced validation and logging
// @Tags Antrol-Peserta
// @Accept json
// @Produce json
// @Param request body models.PesertaPutRequest true "Peserta update request"
// @Success 200 {object} models.PesertaResponse "Peserta updated successfully"
// @Failure 400 {object} models.PesertaResponse "Bad request - validation error"
// @Failure 422 {object} models.PesertaResponse "Unprocessable entity - business logic error"
// @Failure 500 {object} models.PesertaResponse "Internal server error"
// @Router /api/v1/antrol/peserta [put]
func (h *PesertaHandler) UpdatePeserta(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
h.logger.Info("Updating Peserta", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
})
var req models.PesertaPutRequest
req.RequestID = requestID
req.Timestamp = startTime
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON for update", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT",
"Format request tidak valid", err.Error(), requestID)
return
}
if err := req.Validate(); err != nil {
h.logger.Error("Update validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR",
"Validasi gagal", err.Error(), requestID)
return
}
if err := h.validator.Struct(&req); err != nil {
h.logger.Error("Struct validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR",
"Validasi struktur gagal", h.formatValidationError(err), requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
var rawResponse models.BpjsRawResponse
if err := h.service.Put(ctx, "PESERTA/2.0/update", req, &rawResponse); err != nil {
h.logger.Error("Failed to update Peserta", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
statusCode, errorCode := h.categorizeError(err)
h.sendErrorResponse(c, statusCode, errorCode,
"Gagal memperbarui Peserta", err.Error(), requestID)
return
}
if rawResponse.MetaData.Code != "200" {
h.logger.Warn("BPJS update returned error", map[string]interface{}{
"bpjs_code": rawResponse.MetaData.Code,
"bpjs_message": rawResponse.MetaData.Message,
"request_id": requestID,
})
statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code)
h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code,
rawResponse.MetaData.Message, "", requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("Peserta updated successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
})
h.sendSuccessResponse(c, "Peserta berhasil diperbarui", rawResponse.Response, requestID)
}
// DeletePeserta deletes an existing Peserta with comprehensive validation
// @Summary Delete an existing PESERTA
// @Description Delete a Peserta by ID with enhanced validation and logging
// @Tags Antrol-Peserta
// @Accept json
// @Produce json
// @Param id path string true "Peserta ID"
// @Param user query string true "User identifier"
// @Success 200 {object} models.PesertaResponse "Peserta deleted successfully"
// @Failure 400 {object} models.PesertaResponse "Bad request - missing parameters"
// @Failure 422 {object} models.PesertaResponse "Unprocessable entity - business logic error"
// @Failure 500 {object} models.PesertaResponse "Internal server error"
// @Router /api/v1/antrol/peserta/{id} [delete]
func (h *PesertaHandler) DeletePeserta(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
id := c.Param("id")
user := c.Query("user")
h.logger.Info("Deleting Peserta", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"id": id,
"user": user,
})
// Validate parameters
if id == "" {
h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER",
"Parameter ID wajib diisi", "", requestID)
return
}
if user == "" {
h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER",
"Parameter user wajib diisi", "", requestID)
return
}
req := models.PesertaDeleteRequest{
BaseRequest: models.BaseRequest{
RequestID: requestID,
Timestamp: startTime,
},
TPeserta: models.PesertaDeleteData{
ID: id,
User: user,
},
}
if err := req.Validate(); err != nil {
h.logger.Error("Delete validation failed", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR",
"Validasi gagal", err.Error(), requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
var rawResponse models.BpjsRawResponse
if err := h.service.Delete(ctx, "PESERTA/2.0/delete", req); err != nil {
h.logger.Error("Failed to delete Peserta", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
})
statusCode, errorCode := h.categorizeError(err)
h.sendErrorResponse(c, statusCode, errorCode,
"Gagal menghapus Peserta", err.Error(), requestID)
return
}
if rawResponse.MetaData.Code != "200" {
h.logger.Warn("BPJS delete returned error", map[string]interface{}{
"bpjs_code": rawResponse.MetaData.Code,
"bpjs_message": rawResponse.MetaData.Message,
"request_id": requestID,
"id": id,
})
statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code)
h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code,
rawResponse.MetaData.Message, "", requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("Peserta deleted successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"id": id,
})
h.sendSuccessResponse(c, "Peserta berhasil dihapus", rawResponse.Response, requestID)
}
// GetPeserta retrieves Peserta details with comprehensive error handling
// @Summary Get an existing PESERTA
// @Description Retrieve a Peserta by ID with enhanced validation and logging
// @Tags Antrol-Peserta
// @Accept json
// @Produce json
// @Param id path string true "Peserta ID"
// @Success 200 {object} models.PesertaResponse "Data Peserta retrieved successfully"
// @Failure 400 {object} models.PesertaResponse "Bad request - invalid ID"
// @Failure 404 {object} models.PesertaResponse "Peserta not found"
// @Failure 500 {object} models.PesertaResponse "Internal server error"
// @Router /api/v1/antrol/peserta/{id} [get]
func (h *PesertaHandler) GetPeserta(c *gin.Context) {
requestID := uuid.New().String()
startTime := time.Now()
id := c.Param("id")
h.logger.Info("Getting Peserta", map[string]interface{}{
"request_id": requestID,
"timestamp": startTime,
"id": id,
})
if id == "" {
h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER",
"Parameter ID wajib diisi", "", requestID)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
endpoint := fmt.Sprintf("PESERTA/%s", id)
var rawResponse models.BpjsRawResponse
if err := h.service.Get(ctx, endpoint, &rawResponse); err != nil {
h.logger.Error("Failed to get Peserta", map[string]interface{}{
"error": err.Error(),
"request_id": requestID,
"id": id,
})
statusCode, errorCode := h.categorizeError(err)
h.sendErrorResponse(c, statusCode, errorCode,
"Gagal mengambil data Peserta", err.Error(), requestID)
return
}
if rawResponse.MetaData.Code != "200" {
// Handle specific BPJS error codes
if rawResponse.MetaData.Code == "201" {
h.logger.Info("Peserta not found", map[string]interface{}{
"request_id": requestID,
"id": id,
})
h.sendErrorResponse(c, http.StatusNotFound, "DATA_NOT_FOUND",
"Data Peserta tidak ditemukan", rawResponse.MetaData.Message, requestID)
return
}
h.logger.Warn("BPJS get returned error", map[string]interface{}{
"bpjs_code": rawResponse.MetaData.Code,
"bpjs_message": rawResponse.MetaData.Message,
"request_id": requestID,
"id": id,
})
statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code)
h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code,
rawResponse.MetaData.Message, "", requestID)
return
}
duration := time.Since(startTime)
h.logger.Info("Peserta retrieved successfully", map[string]interface{}{
"request_id": requestID,
"duration": duration.String(),
"id": id,
})
h.sendSuccessResponse(c, "Data Peserta berhasil diambil", rawResponse.Response, requestID)
}
// Helper methods for PesertaHandler
func (h *PesertaHandler) sendSuccessResponse(c *gin.Context, message string, data interface{}, requestID string) {
response := models.PesertaResponse{
BaseResponse: models.BaseResponse{
Status: "success",
Message: message,
Data: data,
Metadata: &models.ResponseMetadata{
Timestamp: time.Now(),
Version: "2.0",
RequestID: requestID,
},
},
}
c.JSON(http.StatusOK, response)
}
func (h *PesertaHandler) sendErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) {
response := models.PesertaResponse{
BaseResponse: models.BaseResponse{
Status: "error",
Message: message,
Error: &models.ErrorResponse{
Code: errorCode,
Message: message,
Details: details,
},
Metadata: &models.ResponseMetadata{
Timestamp: time.Now(),
Version: "2.0",
RequestID: requestID,
},
},
}
c.JSON(statusCode, response)
}
func (h *PesertaHandler) formatValidationError(err error) string {
if validationErrors, ok := err.(validator.ValidationErrors); ok {
var messages []string
for _, e := range validationErrors {
switch e.Tag() {
case "required":
messages = append(messages, fmt.Sprintf("%s wajib diisi", e.Field()))
case "min":
messages = append(messages, fmt.Sprintf("%s minimal %s karakter", e.Field(), e.Param()))
case "max":
messages = append(messages, fmt.Sprintf("%s maksimal %s karakter", e.Field(), e.Param()))
case "oneof":
messages = append(messages, fmt.Sprintf("%s harus salah satu dari: %s", e.Field(), e.Param()))
default:
messages = append(messages, fmt.Sprintf("%s tidak valid", e.Field()))
}
}
return fmt.Sprintf("Validasi gagal: %v", messages)
}
return err.Error()
}
func (h *PesertaHandler) categorizeError(err error) (int, string) {
if err == nil {
return http.StatusOK, "SUCCESS"
}
errStr := err.Error()
if h.isTimeoutError(err) {
return http.StatusRequestTimeout, "REQUEST_TIMEOUT"
}
if h.isNetworkError(err) {
return http.StatusBadGateway, "NETWORK_ERROR"
}
if h.isAuthError(errStr) {
return http.StatusUnauthorized, "AUTH_ERROR"
}
return http.StatusInternalServerError, "INTERNAL_ERROR"
}
func (h *PesertaHandler) mapBpjsCodeToHttpStatus(bpjsCode string) int {
switch bpjsCode {
case "200":
return http.StatusOK
case "201":
return http.StatusNotFound
case "202":
return http.StatusBadRequest
case "400":
return http.StatusBadRequest
case "401":
return http.StatusUnauthorized
case "403":
return http.StatusForbidden
case "404":
return http.StatusNotFound
case "500":
return http.StatusInternalServerError
default:
return http.StatusUnprocessableEntity
}
}
func (h *PesertaHandler) isTimeoutError(err error) bool {
return err != nil && (err.Error() == "context deadline exceeded" ||
err.Error() == "timeout")
}
func (h *PesertaHandler) isNetworkError(err error) bool {
return err != nil && (err.Error() == "connection refused" ||
err.Error() == "no such host")
}
func (h *PesertaHandler) isAuthError(errStr string) bool {
return errStr == "unauthorized" || errStr == "invalid credentials"
}

View File

@@ -0,0 +1,164 @@
package handlers
import (
"context"
"fmt"
"net/http"
"time"
"api-service/internal/config"
models "api-service/internal/models/vclaim"
services "api-service/internal/services/bpjs"
"github.com/gin-gonic/gin"
)
// PesertaHandler handles peserta BPJS services
type PesertaHandler struct {
service services.VClaimService
}
// NewPesertaHandler creates a new PesertaHandler
func NewPesertaHandler(cfg config.BpjsConfig) *PesertaHandler {
return &PesertaHandler{
service: services.NewService(cfg),
}
}
// CreatePeserta godoc
// @Summary Create a new PESERTA
// @Description Create a new Peserta in BPJS system
// @Tags vclaim-peserta
// @Accept json
// @Produce json
// @Param request body models.PesertaPostRequest true "Peserta creation request"
// @Success 200 {object} models.PesertaResponse "Peserta created successfully"
// @Failure 400 {object} gin.H "Invalid request"
// @Failure 500 {object} gin.H "Internal server error"
// @Router /api/v1/vclaim/peserta [post]
func (h *PesertaHandler) CreatePeserta(c *gin.Context) {
var req models.PesertaPostRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()})
return
}
ctx, cancel := context.WithTimeout(c, 30*time.Second)
defer cancel()
var result map[string]interface{}
if err := h.service.Post(ctx, "PESERTA/2.0/insert", req, &result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed", "message": err.Error()})
return
}
c.JSON(http.StatusOK, models.PesertaResponse{
Message: "Peserta berhasil dibuat",
Data: result,
})
}
// UpdatePeserta godoc
// @Summary Update an existing PESERTA
// @Description Update an existing Peserta in BPJS system
// @Tags vclaim-peserta
// @Accept json
// @Produce json
// @Param request body models.PesertaPutRequest true "Peserta update request"
// @Success 200 {object} models.PesertaResponse "Peserta updated successfully"
// @Failure 400 {object} gin.H "Invalid request"
// @Failure 500 {object} gin.H "Internal server error"
// @Router /api/v1/vclaim/peserta [put]
func (h *PesertaHandler) UpdatePeserta(c *gin.Context) {
var req models.PesertaPutRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()})
return
}
ctx, cancel := context.WithTimeout(c, 30*time.Second)
defer cancel()
var result map[string]interface{}
if err := h.service.Put(ctx, "PESERTA/2.0/update", req, &result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed", "message": err.Error()})
return
}
c.JSON(http.StatusOK, models.PesertaResponse{
Message: "Peserta berhasil diperbarui",
Data: result,
})
}
// DeletePeserta godoc
// @Summary Delete an existing PESERTA
// @Description Delete a Peserta by ID
// @Tags vclaim-peserta
// @Accept json
// @Produce json
// @Param id path string true "Peserta ID"
// @Param user query string true "User"
// @Success 200 {object} models.PesertaResponse "Peserta deleted successfully"
// @Failure 400 {object} gin.H "Invalid request"
// @Failure 500 {object} gin.H "Internal server error"
// @Router /api/v1/vclaim/peserta/{id} [delete]
func (h *PesertaHandler) DeletePeserta(c *gin.Context) {
id := c.Param("id")
user := c.Query("user")
if id == "" || user == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id & user required"})
return
}
body := models.PesertaDeleteRequest{}
body.TPeserta.ID = id
body.TPeserta.User = user
ctx, cancel := context.WithTimeout(c, 30*time.Second)
defer cancel()
if err := h.service.Delete(ctx, "PESERTA/2.0/delete", body); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed", "message": err.Error()})
return
}
c.JSON(http.StatusOK, models.PesertaResponse{
Message: "Peserta berhasil dihapus",
Data: nil,
})
}
// GetPeserta godoc
// @Summary Get an existing PESERTA
// @Description Retrieve a Peserta by ID
// @Tags vclaim-peserta
// @Accept json
// @Produce json
// @Param id path string true "Peserta ID"
// @Success 200 {object} models.PesertaResponse "Data Peserta retrieved successfully"
// @Failure 400 {object} gin.H "Invalid request"
// @Failure 500 {object} gin.H "Internal server error"
// @Router /api/v1/vclaim/peserta/{id} [get]
func (h *PesertaHandler) GetPeserta(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
return
}
ctx, cancel := context.WithTimeout(c, 30*time.Second)
defer cancel()
endpoint := fmt.Sprintf("PESERTA/%s", id)
var result map[string]interface{}
if err := h.service.Get(ctx, endpoint, &result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "fetch failed", "message": err.Error()})
return
}
c.JSON(http.StatusOK, models.PesertaResponse{
Message: "Data Peserta berhasil diambil",
Data: result,
})
}