From f7fcfa623e670dc533bb378912829c73a3593e63 Mon Sep 17 00:00:00 2001 From: s Date: Mon, 3 Nov 2025 21:17:12 -0500 Subject: hi --- internal/client/skiptrace.go | 179 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 internal/client/skiptrace.go (limited to 'internal/client/skiptrace.go') 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 +} -- cgit v1.2.3