25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

196 satır
5.7KB

  1. package qcclient
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "net/http"
  10. "strings"
  11. "time"
  12. )
  13. type Client interface {
  14. Health(ctx context.Context) error
  15. ListAITemplates(ctx context.Context) ([]Template, error)
  16. GetTemplate(ctx context.Context, templateID int64) (*Template, error)
  17. GetTemplateSchema(ctx context.Context, templateID int64) (json.RawMessage, error)
  18. GenerateContent(ctx context.Context, req GenerateContentRequest) (GenerateContentData, json.RawMessage, error)
  19. CreateSite(ctx context.Context, req CreateSiteRequest) (*CreateSiteResponseData, json.RawMessage, error)
  20. GetJob(ctx context.Context, jobID int64) (*JobStatusData, json.RawMessage, error)
  21. GetEditorURL(ctx context.Context, siteID int64) (*SiteEditorLoginData, json.RawMessage, error)
  22. }
  23. type HTTPClient struct {
  24. baseURL string
  25. token string
  26. client *http.Client
  27. logger *slog.Logger
  28. }
  29. func New(baseURL, token string, timeout time.Duration, logger *slog.Logger) *HTTPClient {
  30. if timeout <= 0 {
  31. timeout = 10 * time.Second
  32. }
  33. return &HTTPClient{
  34. baseURL: strings.TrimRight(baseURL, "/"),
  35. token: token,
  36. client: &http.Client{Timeout: timeout},
  37. logger: logger,
  38. }
  39. }
  40. func (c *HTTPClient) Health(ctx context.Context) error {
  41. _, _, err := c.doJSON(ctx, http.MethodGet, "/health", nil)
  42. return err
  43. }
  44. func (c *HTTPClient) ListAITemplates(ctx context.Context) ([]Template, error) {
  45. body, _, err := c.doJSON(ctx, http.MethodGet, "/templates?type=ai", nil)
  46. if err != nil {
  47. return nil, err
  48. }
  49. var rsp APIResponse[[]Template]
  50. if err := json.Unmarshal(body, &rsp); err != nil {
  51. return nil, fmt.Errorf("decode templates response: %w", err)
  52. }
  53. return rsp.Data, nil
  54. }
  55. func (c *HTTPClient) GetTemplate(ctx context.Context, templateID int64) (*Template, error) {
  56. body, _, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/templates/%d", templateID), nil)
  57. if err != nil {
  58. return nil, err
  59. }
  60. var rsp APIResponse[Template]
  61. if err := json.Unmarshal(body, &rsp); err != nil {
  62. return nil, fmt.Errorf("decode template response: %w", err)
  63. }
  64. return &rsp.Data, nil
  65. }
  66. func (c *HTTPClient) GetTemplateSchema(ctx context.Context, templateID int64) (json.RawMessage, error) {
  67. body, _, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/templates/%d/schema", templateID), nil)
  68. if err != nil {
  69. return nil, err
  70. }
  71. return json.RawMessage(body), nil
  72. }
  73. func (c *HTTPClient) GenerateContent(ctx context.Context, req GenerateContentRequest) (GenerateContentData, json.RawMessage, error) {
  74. body, raw, err := c.doJSON(ctx, http.MethodPost, "/generate-content", req)
  75. if err != nil {
  76. return nil, nil, err
  77. }
  78. var rsp APIResponse[GenerateContentData]
  79. if err := json.Unmarshal(body, &rsp); err != nil {
  80. return nil, raw, fmt.Errorf("decode generate-content response: %w", err)
  81. }
  82. return rsp.Data, raw, nil
  83. }
  84. func (c *HTTPClient) CreateSite(ctx context.Context, req CreateSiteRequest) (*CreateSiteResponseData, json.RawMessage, error) {
  85. body, raw, err := c.doJSON(ctx, http.MethodPost, "/sites", req)
  86. if err != nil {
  87. return nil, nil, err
  88. }
  89. var rsp APIResponse[CreateSiteResponseData]
  90. if err := json.Unmarshal(body, &rsp); err != nil {
  91. return nil, raw, fmt.Errorf("decode create site response: %w", err)
  92. }
  93. return &rsp.Data, raw, nil
  94. }
  95. func (c *HTTPClient) GetJob(ctx context.Context, jobID int64) (*JobStatusData, json.RawMessage, error) {
  96. body, raw, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/jobs/%d", jobID), nil)
  97. if err != nil {
  98. return nil, nil, err
  99. }
  100. var rsp APIResponse[JobStatusData]
  101. if err := json.Unmarshal(body, &rsp); err != nil {
  102. return nil, raw, fmt.Errorf("decode job response: %w", err)
  103. }
  104. return &rsp.Data, raw, nil
  105. }
  106. func (c *HTTPClient) GetEditorURL(ctx context.Context, siteID int64) (*SiteEditorLoginData, json.RawMessage, error) {
  107. body, raw, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/sites/%d/editor-url", siteID), nil)
  108. if err != nil {
  109. return nil, nil, err
  110. }
  111. var rsp APIResponse[SiteEditorLoginData]
  112. if err := json.Unmarshal(body, &rsp); err != nil {
  113. return nil, raw, fmt.Errorf("decode editor-url response: %w", err)
  114. }
  115. return &rsp.Data, raw, nil
  116. }
  117. func (c *HTTPClient) doJSON(ctx context.Context, method, path string, payload any) ([]byte, json.RawMessage, error) {
  118. var body io.Reader
  119. if payload != nil {
  120. b, err := json.Marshal(payload)
  121. if err != nil {
  122. return nil, nil, fmt.Errorf("marshal request: %w", err)
  123. }
  124. body = bytes.NewReader(b)
  125. }
  126. req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, body)
  127. if err != nil {
  128. return nil, nil, fmt.Errorf("build request: %w", err)
  129. }
  130. req.Header.Set("Authorization", "Bearer "+c.token)
  131. req.Header.Set("Accept", "application/json")
  132. if payload != nil {
  133. req.Header.Set("Content-Type", "application/json")
  134. }
  135. start := time.Now()
  136. res, err := c.client.Do(req)
  137. if err != nil {
  138. return nil, nil, fmt.Errorf("do request: %w", err)
  139. }
  140. defer res.Body.Close()
  141. respBody, err := io.ReadAll(res.Body)
  142. if err != nil {
  143. return nil, nil, fmt.Errorf("read response: %w", err)
  144. }
  145. c.logger.Info("qc request",
  146. "method", method,
  147. "path", path,
  148. "status", res.StatusCode,
  149. "duration_ms", time.Since(start).Milliseconds(),
  150. "auth", "Bearer [REDACTED]",
  151. )
  152. if res.StatusCode >= 400 {
  153. return nil, json.RawMessage(respBody), c.mapHTTPError(res.StatusCode, respBody)
  154. }
  155. return respBody, json.RawMessage(respBody), nil
  156. }
  157. func (c *HTTPClient) mapHTTPError(statusCode int, raw []byte) error {
  158. var apiErr APIError
  159. _ = json.Unmarshal(raw, &apiErr)
  160. err := &HTTPError{
  161. StatusCode: statusCode,
  162. APIError: &apiErr,
  163. RawBody: raw,
  164. }
  165. switch statusCode {
  166. case http.StatusBadRequest:
  167. return fmt.Errorf("%w: %w", ErrBadRequest, err)
  168. case http.StatusUnauthorized:
  169. return fmt.Errorf("%w: %w", ErrUnauthorized, err)
  170. case http.StatusNotFound:
  171. return fmt.Errorf("%w: %w", ErrNotFound, err)
  172. default:
  173. return err
  174. }
  175. }