108 lines
3.4 KiB
Go
108 lines
3.4 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 an image file via multipart form and an optional threshold parameter.
|
|
// If threshold is not provided, uses the default 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())
|
|
|
|
// Get threshold from form or use default from config
|
|
threshold := c.PostForm("threshold")
|
|
if threshold == "" {
|
|
threshold = h.config.Dukcapil.Threshold
|
|
}
|
|
|
|
// Build request with config values
|
|
req := &contract.FaceMatchRequest{
|
|
TransactionID: transactionID,
|
|
TransactionSource: h.config.Dukcapil.TransactionSource,
|
|
Threshold: 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,
|
|
},
|
|
})
|
|
}
|