update api
This commit is contained in:
parent
2c43e758f5
commit
c3317dd9ee
@ -4,13 +4,15 @@ import "time"
|
|||||||
|
|
||||||
// Dukcapil holds configuration for the Dukcapil Face Recognition (1:N) API.
|
// Dukcapil holds configuration for the Dukcapil Face Recognition (1:N) API.
|
||||||
type Dukcapil struct {
|
type Dukcapil struct {
|
||||||
BaseURL string `mapstructure:"base_url"`
|
BaseURL string `mapstructure:"base_url"`
|
||||||
CustomerID string `mapstructure:"customer_id"`
|
CustomerID string `mapstructure:"customer_id"`
|
||||||
Methode string `mapstructure:"methode"`
|
Methode string `mapstructure:"methode"`
|
||||||
UserID string `mapstructure:"user_id"`
|
UserID string `mapstructure:"user_id"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
DefaultIP string `mapstructure:"default_ip"`
|
DefaultIP string `mapstructure:"default_ip"`
|
||||||
TimeoutSecond int `mapstructure:"timeout_second"`
|
TimeoutSecond int `mapstructure:"timeout_second"`
|
||||||
|
TransactionSource string `mapstructure:"transaction_source"`
|
||||||
|
Threshold string `mapstructure:"threshold"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dukcapil) Timeout() time.Duration {
|
func (d *Dukcapil) Timeout() time.Duration {
|
||||||
|
|||||||
@ -33,5 +33,7 @@ dukcapil:
|
|||||||
methode: "CALL_FN"
|
methode: "CALL_FN"
|
||||||
user_id: "281020241202039900305241000011252"
|
user_id: "281020241202039900305241000011252"
|
||||||
password: "Fjskdhv35$%"
|
password: "Fjskdhv35$%"
|
||||||
default_ip: "10.160.86.53"
|
default_ip: "10.160.86.48"
|
||||||
timeout_second: 30
|
timeout_second: 30
|
||||||
|
transaction_source: "eslogad"
|
||||||
|
threshold: "10"
|
||||||
|
|||||||
@ -33,7 +33,7 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
|
|
||||||
dukcapilClient := client.NewDukcapilClient(cfg.Dukcapil)
|
dukcapilClient := client.NewDukcapilClient(cfg.Dukcapil)
|
||||||
dukcapilService := service.NewDukcapilService(dukcapilClient)
|
dukcapilService := service.NewDukcapilService(dukcapilClient)
|
||||||
dukcapilHandler := handler.NewDukcapilHandler(dukcapilService)
|
dukcapilHandler := handler.NewDukcapilHandler(dukcapilService, cfg)
|
||||||
|
|
||||||
a.router = router.NewRouter(
|
a.router = router.NewRouter(
|
||||||
cfg,
|
cfg,
|
||||||
|
|||||||
@ -39,37 +39,31 @@ func (c *DukcapilClient) FaceMatch(ctx context.Context, req *contract.FaceMatchR
|
|||||||
return nil, errors.New("dukcapil: incomplete configuration")
|
return nil, errors.New("dukcapil: incomplete configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load PEM public key from file
|
||||||
|
pemBytes, err := os.ReadFile("infra/990030524100001.pem")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dukcapil: failed to read PEM file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
ip := req.IP
|
// Encrypt UserID and Password
|
||||||
if strings.TrimSpace(ip) == "" {
|
encryptedUserID, err := util.EncryptWithPublicKey(c.cfg.UserID, pemBytes)
|
||||||
ip = c.cfg.DefaultIP
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("dukcapil: encrypt user_id: %w", err)
|
||||||
|
}
|
||||||
|
encryptedPassword, err := util.EncryptWithPublicKey(c.cfg.Password, pemBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dukcapil: encrypt password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Load PEM public key from file
|
body := contract.DukcapilFaceRequest{
|
||||||
pemBytes, err := os.ReadFile("infra/990030524100001.pem")
|
TransactionID: req.TransactionID,
|
||||||
if err != nil {
|
TransactionSource: req.TransactionSource,
|
||||||
return nil, fmt.Errorf("dukcapil: failed to read PEM file: %w", err)
|
Threshold: req.Threshold,
|
||||||
}
|
Image: req.Image,
|
||||||
|
UserID: encryptedUserID,
|
||||||
// Encrypt UserID and Password
|
Password: encryptedPassword,
|
||||||
encryptedUserID, err := util.EncryptWithPublicKey(c.cfg.UserID, pemBytes)
|
IP: req.IP,
|
||||||
if err != nil {
|
}
|
||||||
return nil, fmt.Errorf("dukcapil: encrypt user_id: %w", err)
|
|
||||||
}
|
|
||||||
encryptedPassword, err := util.EncryptWithPublicKey(c.cfg.Password, pemBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dukcapil: encrypt password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
body := contract.DukcapilFaceRequest{
|
|
||||||
TransactionID: req.TransactionID,
|
|
||||||
TransactionSource: req.TransactionSource,
|
|
||||||
Threshold: req.Threshold,
|
|
||||||
Image: req.Image,
|
|
||||||
UserID: encryptedUserID,
|
|
||||||
Password: encryptedPassword,
|
|
||||||
IP: ip,
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := json.Marshal(body)
|
payload, err := json.Marshal(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -3,15 +3,15 @@ package contract
|
|||||||
// FaceMatchRequest is the inbound payload from clients of this service to
|
// FaceMatchRequest is the inbound payload from clients of this service to
|
||||||
// trigger a Dukcapil 1:N face recognition lookup.
|
// trigger a Dukcapil 1:N face recognition lookup.
|
||||||
//
|
//
|
||||||
// Image must already be a base64 (no data:image prefix) representation of a
|
// Only the image file is required from the client. All other parameters
|
||||||
// jpg/png file. Threshold is forwarded to Dukcapil (1..20). IP is optional;
|
// (transaction_id, transaction_source, threshold, ip) are generated or
|
||||||
// when empty the configured default IP will be used.
|
// retrieved from configuration in the backend.
|
||||||
type FaceMatchRequest struct {
|
type FaceMatchRequest struct {
|
||||||
TransactionID string `json:"transaction_id" validate:"required,max=20"`
|
TransactionID string `json:"transaction_id"`
|
||||||
TransactionSource string `json:"transaction_source" validate:"required,max=50"`
|
TransactionSource string `json:"transaction_source"`
|
||||||
Threshold string `json:"threshold" validate:"required"`
|
Threshold string `json:"threshold"`
|
||||||
Image string `json:"image" validate:"required"`
|
Image string `json:"image"`
|
||||||
IP string `json:"ip,omitempty"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DukcapilFaceRequest is the exact JSON body sent to the Dukcapil
|
// DukcapilFaceRequest is the exact JSON body sent to the Dukcapil
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"time"
|
||||||
|
|
||||||
|
"go-backend-template/config"
|
||||||
"go-backend-template/internal/constants"
|
"go-backend-template/internal/constants"
|
||||||
"go-backend-template/internal/contract"
|
"go-backend-template/internal/contract"
|
||||||
"go-backend-template/internal/logger"
|
"go-backend-template/internal/logger"
|
||||||
@ -14,39 +18,61 @@ import (
|
|||||||
|
|
||||||
type DukcapilHandler struct {
|
type DukcapilHandler struct {
|
||||||
dukcapilService DukcapilService
|
dukcapilService DukcapilService
|
||||||
|
config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDukcapilHandler(dukcapilService DukcapilService) *DukcapilHandler {
|
func NewDukcapilHandler(dukcapilService DukcapilService, cfg *config.Config) *DukcapilHandler {
|
||||||
return &DukcapilHandler{dukcapilService: dukcapilService}
|
return &DukcapilHandler{
|
||||||
|
dukcapilService: dukcapilService,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FaceMatch handles POST /api/v1/dukcapil/face-match (1:N face recognition).
|
// 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) {
|
func (h *DukcapilHandler) FaceMatch(c *gin.Context) {
|
||||||
var req contract.FaceMatchRequest
|
// Parse multipart form
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
file, err := c.FormFile("image")
|
||||||
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> request binding failed")
|
if err != nil {
|
||||||
h.sendValidationError(c, "Invalid request body", constants.MalformedFieldErrorCode)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(req.TransactionID) == "" {
|
// Open the uploaded file
|
||||||
h.sendValidationError(c, "transaction_id is required", constants.MissingFieldErrorCode)
|
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
|
return
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(req.TransactionSource) == "" {
|
defer src.Close()
|
||||||
h.sendValidationError(c, "transaction_source is required", constants.MissingFieldErrorCode)
|
|
||||||
return
|
// Read file content
|
||||||
}
|
imageBytes, err := io.ReadAll(src)
|
||||||
if strings.TrimSpace(req.Threshold) == "" {
|
if err != nil {
|
||||||
h.sendValidationError(c, "threshold is required", constants.MissingFieldErrorCode)
|
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> failed to read image bytes")
|
||||||
return
|
h.sendValidationError(c, "failed to read image file", constants.MalformedFieldErrorCode)
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.Image) == "" {
|
|
||||||
h.sendValidationError(c, "image is required (base64-encoded)", constants.MissingFieldErrorCode)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.dukcapilService.FaceMatch(c.Request.Context(), &req)
|
// 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 {
|
if err != nil {
|
||||||
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> upstream call failed")
|
logger.FromContext(c.Request.Context()).WithError(err).Error("DukcapilHandler::FaceMatch -> upstream call failed")
|
||||||
c.JSON(http.StatusBadGateway, &contract.ErrorResponse{
|
c.JSON(http.StatusBadGateway, &contract.ErrorResponse{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user