dukcapil/internal/handler/dukcapil_handler.go
Aditya Siregar c3317dd9ee update api
2026-05-08 10:06:43 +07:00

102 lines
3.2 KiB
Go

package handler
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"time"
"go-backend-template/config"
"go-backend-template/internal/constants"
"go-backend-template/internal/contract"
"go-backend-template/internal/logger"
"go-backend-template/internal/util"
"github.com/gin-gonic/gin"
)
type DukcapilHandler struct {
dukcapilService DukcapilService
config *config.Config
}
func NewDukcapilHandler(dukcapilService DukcapilService, cfg *config.Config) *DukcapilHandler {
return &DukcapilHandler{
dukcapilService: dukcapilService,
config: cfg,
}
}
// FaceMatch handles POST /api/v1/dukcapil/face-match (1:N face recognition).
// Accepts only an image file via multipart form. All other parameters are
// generated or retrieved from configuration.
func (h *DukcapilHandler) FaceMatch(c *gin.Context) {
// Parse multipart form
file, err := c.FormFile("image")
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> failed to get image file")
h.sendValidationError(c, "image file is required", constants.MissingFieldErrorCode)
return
}
// Open the uploaded file
src, err := file.Open()
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> failed to open image file")
h.sendValidationError(c, "failed to read image file", constants.MalformedFieldErrorCode)
return
}
defer src.Close()
// Read file content
imageBytes, err := io.ReadAll(src)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> failed to read image bytes")
h.sendValidationError(c, "failed to read image file", constants.MalformedFieldErrorCode)
return
}
// Convert to base64
imageBase64 := base64.StdEncoding.EncodeToString(imageBytes)
// Generate transaction_id (timestamp-based random ID)
transactionID := fmt.Sprintf("TXN%d", time.Now().UnixNano())
// Build request with config values
req := &contract.FaceMatchRequest{
TransactionID: transactionID,
TransactionSource: h.config.Dukcapil.TransactionSource,
Threshold: h.config.Dukcapil.Threshold,
Image: imageBase64,
IP: h.config.Dukcapil.DefaultIP,
}
res, err := h.dukcapilService.FaceMatch(c.Request.Context(), req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> upstream call failed")
c.JSON(http.StatusBadGateway, &contract.ErrorResponse{
Error: "upstream_error",
Message: err.Error(),
Code: http.StatusBadGateway,
Details: map[string]interface{}{"entity": constants.DukcapilHandlerEntity},
})
return
}
logger.FromContext(c.Request.Context()).Infof("DukcapilHandler::FaceMatch -> tid=%s errorCode=%s matches=%d", res.TID, res.ErrorCode, len(res.Matches))
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(res), "DukcapilHandler::FaceMatch")
}
func (h *DukcapilHandler) sendValidationError(c *gin.Context, message, code string) {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: "validation_error",
Message: message,
Code: http.StatusBadRequest,
Details: map[string]interface{}{
"error_code": code,
"entity": constants.DukcapilHandlerEntity,
},
})
}