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 }