summaryrefslogtreecommitdiffstats
path: root/internal/client/skiptrace.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/client/skiptrace.go')
-rw-r--r--internal/client/skiptrace.go179
1 files changed, 179 insertions, 0 deletions
diff --git a/internal/client/skiptrace.go b/internal/client/skiptrace.go
new file mode 100644
index 0000000..b1d5008
--- /dev/null
+++ b/internal/client/skiptrace.go
@@ -0,0 +1,179 @@
+package client
+
+import (
+ "bufio"
+ "bytes"
+ "dborg/internal/models"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+func parseSSEResponse(data []byte) ([]byte, error) {
+ scanner := bufio.NewScanner(bytes.NewReader(data))
+
+ const maxScanTokenSize = 10 * 1024 * 1024
+ buf := make([]byte, maxScanTokenSize)
+ scanner.Buffer(buf, maxScanTokenSize)
+
+ var resultData []byte
+ var foundResult bool
+
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ if line == "event: result" {
+ foundResult = true
+ continue
+ }
+
+ if foundResult && strings.HasPrefix(line, "data: ") {
+ resultData = []byte(strings.TrimPrefix(line, "data: "))
+ break
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading SSE response: %w", err)
+ }
+
+ if resultData == nil {
+ return nil, fmt.Errorf("no result event found in SSE response")
+ }
+
+ trimmed := strings.TrimSpace(string(resultData))
+ if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") {
+ return nil, fmt.Errorf("API returned: %s", trimmed)
+ }
+
+ return resultData, nil
+}
+
+func (c *Client) getSSE(path string, params url.Values) ([]byte, error) {
+ fullURL := c.config.BaseURL + path
+ if params != nil && len(params) > 0 {
+ fullURL += "?" + params.Encode()
+ }
+
+ req, err := http.NewRequest("GET", fullURL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("X-API-Key", c.config.APIKey)
+ req.Header.Set("User-Agent", c.config.UserAgent)
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("request failed: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ bodyBytes, _ := io.ReadAll(resp.Body)
+ switch resp.StatusCode {
+ case http.StatusForbidden:
+ return nil, fmt.Errorf("access denied (403): %s - This endpoint requires premium access", string(bodyBytes))
+ case http.StatusUnauthorized:
+ return nil, fmt.Errorf("unauthorized (401): %s - Check your API key", string(bodyBytes))
+ case http.StatusTooManyRequests:
+ return nil, fmt.Errorf("rate limit exceeded (429): %s", string(bodyBytes))
+ case http.StatusBadRequest:
+ return nil, fmt.Errorf("bad request (400): %s", string(bodyBytes))
+ default:
+ return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(bodyBytes))
+ }
+ }
+
+ data, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read response: %w", err)
+ }
+
+ contentType := resp.Header.Get("Content-Type")
+
+ if strings.HasPrefix(string(data), "event:") || strings.Contains(contentType, "text/event-stream") {
+ return parseSSEResponse(data)
+ }
+
+ return data, nil
+}
+
+func (c *Client) SearchPeople(params *models.SkiptraceParams) (*models.SkiptraceResponse, error) {
+ queryParams := url.Values{}
+ queryParams.Set("first_name", params.FirstName)
+ queryParams.Set("last_name", params.LastName)
+
+ if params.City != "" {
+ queryParams.Set("city", params.City)
+ }
+ if params.State != "" {
+ queryParams.Set("state", params.State)
+ }
+ if params.Age != "" {
+ queryParams.Set("age", params.Age)
+ }
+
+ data, err := c.getSSE("/prem/skiptrace/people/search", queryParams)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.SkiptraceResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ return &response, nil
+}
+
+func (c *Client) GetPersonReport(sxKey string, selection int) (*models.SkiptraceReportResponse, error) {
+ path := fmt.Sprintf("/prem/skiptrace/people/report/%s/%d", sxKey, selection)
+
+ data, err := c.getSSE(path, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.SkiptraceReportResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ return &response, nil
+}
+
+func (c *Client) SearchPhone(phone string) (*models.SkiptracePhoneResponse, error) {
+ path := fmt.Sprintf("/prem/skiptrace/phone/%s", phone)
+
+ data, err := c.getSSE(path, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.SkiptracePhoneResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ return &response, nil
+}
+
+func (c *Client) SearchEmail(email string) (*models.SkiptraceEmailResponse, error) {
+ path := fmt.Sprintf("/prem/skiptrace/email/%s", email)
+
+ data, err := c.getSSE(path, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.SkiptraceEmailResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse response: %w", err)
+ }
+
+ return &response, nil
+}