From 76d0cff639988ca506b1dc6e848841944c96b263 Mon Sep 17 00:00:00 2001 From: s Date: Sun, 16 Nov 2025 02:40:58 -0500 Subject: docs: add comprehensive api endpoints documentation and expand cli functionality --- internal/client/admin.go | 14 +++++ internal/client/github.go | 123 ++++++++++++++++++++++++++++++++++++++++++ internal/formatter/account.go | 53 ++++++++++++++++++ internal/formatter/github.go | 29 ++++++++++ internal/models/admin.go | 10 ++++ internal/models/github.go | 14 +++++ 6 files changed, 243 insertions(+) create mode 100644 internal/client/github.go create mode 100644 internal/formatter/account.go create mode 100644 internal/formatter/github.go create mode 100644 internal/models/github.go (limited to 'internal') diff --git a/internal/client/admin.go b/internal/client/admin.go index f4838b7..bf8c5ce 100644 --- a/internal/client/admin.go +++ b/internal/client/admin.go @@ -122,3 +122,17 @@ func (c *Client) ToggleAccount(apiKey string, enable bool) (*models.AdminRespons return &response, nil } + +func (c *Client) GetAccountStats() (*models.AccountStatsResponse, error) { + data, err := c.Get("/me", nil) + if err != nil { + return nil, err + } + + var response models.AccountStatsResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse account stats response: %w", err) + } + + return &response, nil +} diff --git a/internal/client/github.go b/internal/client/github.go new file mode 100644 index 0000000..f8b7097 --- /dev/null +++ b/internal/client/github.go @@ -0,0 +1,123 @@ +package client + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +func (c *Client) SearchGitHubLeads(query string, callback func(result json.RawMessage) error) error { + path := "/github/leads" + + params := url.Values{} + params.Set("q", query) + + fullURL := c.config.BaseURL + path + "?" + params.Encode() + + req, err := http.NewRequest(http.MethodGet, fullURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("User-Agent", c.config.UserAgent) + req.Header.Set("Accept", "application/x-ndjson, application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to execute request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) + } + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + line := scanner.Bytes() + if len(line) == 0 { + continue + } + + if err := callback(json.RawMessage(line)); err != nil { + return err + } + } + + if err := scanner.Err(); err != nil { + if !strings.Contains(err.Error(), "context deadline exceeded") && !strings.Contains(err.Error(), "timeout") { + return fmt.Errorf("stream reading error: %w", err) + } + } + + return nil +} + +func (c *Client) SearchGitHubLeadsWithParams(query, sort, exclude string, callback func(result json.RawMessage) error) error { + path := "/github/leads" + + params := url.Values{} + params.Set("q", query) + if sort != "" { + params.Set("sort", sort) + } + if exclude != "" { + params.Set("exclude", exclude) + } + + fullURL := c.config.BaseURL + path + "?" + params.Encode() + + req, err := http.NewRequest(http.MethodGet, fullURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("User-Agent", c.config.UserAgent) + req.Header.Set("Accept", "application/x-ndjson, application/json") + req.Header.Set("Connection", "keep-alive") + req.ProtoMajor = 1 + req.ProtoMinor = 1 + + http1Client := &http.Client{ + Timeout: c.config.Timeout, + Transport: &http.Transport{ + ForceAttemptHTTP2: false, + }, + } + + resp, err := http1Client.Do(req) + if err != nil { + return fmt.Errorf("failed to execute request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) + } + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + line := scanner.Bytes() + if len(line) == 0 { + continue + } + + if err := callback(json.RawMessage(line)); err != nil { + return err + } + } + + if err := scanner.Err(); err != nil { + if !strings.Contains(err.Error(), "context deadline exceeded") && !strings.Contains(err.Error(), "timeout") { + return fmt.Errorf("stream reading error: %w", err) + } + } + + return nil +} diff --git a/internal/formatter/account.go b/internal/formatter/account.go new file mode 100644 index 0000000..67f92bb --- /dev/null +++ b/internal/formatter/account.go @@ -0,0 +1,53 @@ +package formatter + +import ( + "encoding/json" + "fmt" + "git.db.org.ai/dborg/internal/models" + "strings" +) + +func FormatAccountStats(stats *models.AccountStatsResponse, jsonOutput bool) (string, error) { + if jsonOutput { + data, err := json.Marshal(stats) + if err != nil { + return "", fmt.Errorf("failed to marshal account stats: %w", err) + } + return string(data), nil + } + + var output strings.Builder + + if stats.Account != nil { + output.WriteString("Account Information:\n") + output.WriteString(fmt.Sprintf(" Name: %s\n", stats.Account.Name)) + output.WriteString(fmt.Sprintf(" API Key: %s\n", stats.Account.APIKey)) + output.WriteString(fmt.Sprintf(" Credits: %d\n", stats.Account.Credits)) + output.WriteString(fmt.Sprintf(" Unlimited: %t\n", stats.Account.Unlimited)) + output.WriteString(fmt.Sprintf(" Premium: %t\n", stats.Account.IsPremium)) + output.WriteString(fmt.Sprintf(" Admin: %t\n", stats.Account.IsAdmin)) + output.WriteString("\n") + } + + if stats.UsageStats != nil && len(stats.UsageStats) > 0 { + output.WriteString("Usage Statistics:\n") + for service, count := range stats.UsageStats { + output.WriteString(fmt.Sprintf(" %s: %d\n", service, count)) + } + output.WriteString("\n") + } + + if stats.TotalRequests > 0 { + output.WriteString(fmt.Sprintf("Total Requests: %d\n", stats.TotalRequests)) + } + + if stats.CreditsUsed > 0 { + output.WriteString(fmt.Sprintf("Credits Used: %d\n", stats.CreditsUsed)) + } + + if stats.Message != "" { + output.WriteString(fmt.Sprintf("Message: %s\n", stats.Message)) + } + + return output.String(), nil +} diff --git a/internal/formatter/github.go b/internal/formatter/github.go new file mode 100644 index 0000000..1d28af5 --- /dev/null +++ b/internal/formatter/github.go @@ -0,0 +1,29 @@ +package formatter + +import ( + "encoding/json" + "fmt" + "git.db.org.ai/dborg/internal/models" + "strings" +) + +func FormatGitHubLeads(lead *models.GitHubLeadsStreamResponse, jsonOutput bool) (string, error) { + if jsonOutput { + data, err := json.Marshal(lead.Lead) + if err != nil { + return "", fmt.Errorf("failed to marshal GitHub lead: %w", err) + } + return string(data), nil + } + + var output strings.Builder + output.WriteString(fmt.Sprintf("Repository: %s\n", lead.Lead.Repository)) + output.WriteString(fmt.Sprintf("Author: %s\n", lead.Lead.Author)) + output.WriteString(fmt.Sprintf("Email: %s\n", lead.Lead.Email)) + output.WriteString(fmt.Sprintf("Commit: %s\n", lead.Lead.Commit)) + output.WriteString(fmt.Sprintf("Date: %s\n", lead.Lead.Date)) + output.WriteString(fmt.Sprintf("Message: %s\n", lead.Lead.Message)) + output.WriteString("---\n") + + return output.String(), nil +} diff --git a/internal/models/admin.go b/internal/models/admin.go index 22dee9b..852e80f 100644 --- a/internal/models/admin.go +++ b/internal/models/admin.go @@ -36,6 +36,16 @@ type DisableAccountRequest struct { Disabled bool `json:"disabled"` } +type AccountStatsResponse struct { + Success bool `json:"success,omitempty"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` + Account *Account `json:"account,omitempty"` + UsageStats map[string]int `json:"usage_stats,omitempty"` + TotalRequests int `json:"total_requests,omitempty"` + CreditsUsed int `json:"credits_used,omitempty"` +} + type AdminResponse struct { Success bool `json:"success,omitempty"` Message string `json:"message,omitempty"` diff --git a/internal/models/github.go b/internal/models/github.go new file mode 100644 index 0000000..722cbb8 --- /dev/null +++ b/internal/models/github.go @@ -0,0 +1,14 @@ +package models + +type GitHubLeadResult struct { + Repository string `json:"repository"` + Author string `json:"author"` + Email string `json:"email"` + Commit string `json:"commit"` + Date string `json:"date"` + Message string `json:"message"` +} + +type GitHubLeadsStreamResponse struct { + Lead GitHubLeadResult `json:"lead"` +} -- cgit v1.2.3