dukcapil/internal/client/dukcapil_client.go
2026-05-07 04:18:57 +07:00

100 lines
2.5 KiB
Go

package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"go-backend-template/config"
"go-backend-template/internal/contract"
"go-backend-template/internal/logger"
)
// DukcapilClient performs HTTPS calls to the Dukcapil 1:N face recognition endpoint (CALL_FN).
type DukcapilClient struct {
cfg config.Dukcapil
http *http.Client
}
func NewDukcapilClient(cfg config.Dukcapil) *DukcapilClient {
return &DukcapilClient{
cfg: cfg,
http: &http.Client{
Timeout: cfg.Timeout(),
},
}
}
// FaceMatch performs a 1:N face match call. The image must already be base64 encoded.
func (c *DukcapilClient) FaceMatch(ctx context.Context, req *contract.FaceMatchRequest) (*contract.DukcapilFaceResponse, error) {
if c.cfg.BaseURL == "" || c.cfg.CustomerID == "" || c.cfg.Methode == "" {
return nil, errors.New("dukcapil: incomplete configuration")
}
ip := req.IP
if strings.TrimSpace(ip) == "" {
ip = c.cfg.DefaultIP
}
body := contract.DukcapilFaceRequest{
TransactionID: req.TransactionID,
TransactionSource: req.TransactionSource,
Threshold: req.Threshold,
Image: req.Image,
UserID: c.cfg.UserID,
Password: c.cfg.Password,
IP: ip,
}
payload, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("dukcapil: marshal request: %w", err)
}
url := fmt.Sprintf("%s/%s/%s",
strings.TrimRight(c.cfg.BaseURL, "/"),
c.cfg.CustomerID,
c.cfg.Methode,
)
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
if err != nil {
return nil, fmt.Errorf("dukcapil: build request: %w", err)
}
httpReq.Header.Set("Accept", "application/json")
httpReq.Header.Set("Content-Type", "application/json")
start := time.Now()
resp, err := c.http.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("dukcapil: do request: %w", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("dukcapil: read response: %w", err)
}
logger.FromContext(ctx).Infof("DukcapilClient::FaceMatch -> status=%d duration=%s", resp.StatusCode, time.Since(start))
if resp.StatusCode >= 500 {
return nil, fmt.Errorf("dukcapil: upstream status %d: %s", resp.StatusCode, string(respBytes))
}
var out contract.DukcapilFaceResponse
if err := json.Unmarshal(respBytes, &out); err != nil {
return nil, fmt.Errorf("dukcapil: decode response: %w (body=%s)", err, string(respBytes))
}
return &out, nil
}