feat(platform): add cmsctl and document newsletter and shell updates
Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
0f8ed97878
commit
fea6eb51b1
7 files changed
+2408
No files matched your search
@@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxResponseBytes = 64 << 20
|
||||
|
||||
type Client struct {
|
||||
cfg Config
|
||||
http *http.Client
|
||||
token string
|
||||
apiKey string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newClient(cfg Config, token, apiKey string, timeout time.Duration) *Client {
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
http: &http.Client{Timeout: timeout},
|
||||
token: token,
|
||||
apiKey: apiKey,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) cmsURL(endpoint string) string {
|
||||
return joinURL(effectiveBaseURL(c.cfg), "/api", endpoint)
|
||||
}
|
||||
|
||||
func (c *Client) finderURL(endpoint string) string {
|
||||
return joinURL(effectiveFinderURL(c.cfg), "/api", endpoint)
|
||||
}
|
||||
|
||||
// finderAdminURL is deliberately based on BaseURL, never FinderURL. The
|
||||
// backoffice owns the writeradmin surface and is the only supported write path.
|
||||
func (c *Client) finderAdminURL(endpoint string) string {
|
||||
return joinURL(effectiveBaseURL(c.cfg), "/catalog/admin", endpoint)
|
||||
}
|
||||
|
||||
func joinURL(base string, parts ...string) string {
|
||||
u, err := url.Parse(strings.TrimRight(base, "/"))
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return strings.TrimRight(base, "/") + "/" + strings.TrimLeft(path.Join(parts...), "/")
|
||||
}
|
||||
joined := strings.TrimRight(u.Path, "/")
|
||||
for _, part := range parts {
|
||||
joined = path.Join(joined, "/"+strings.Trim(part, "/"))
|
||||
}
|
||||
u.Path = joined
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func escapePathSegment(value string) string {
|
||||
return url.PathEscape(value)
|
||||
}
|
||||
|
||||
func (c *Client) request(ctx context.Context, method, endpoint string, body []byte, contentType string) ([]byte, int, error) {
|
||||
var reader io.Reader
|
||||
if body != nil {
|
||||
reader = bytes.NewReader(body)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, endpoint, reader)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/json, application/jsonl+json, text/plain")
|
||||
if body != nil {
|
||||
if contentType == "" {
|
||||
contentType = "application/json"
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
if c.apiKey != "" {
|
||||
req.Header.Set("X-API-Key", c.apiKey)
|
||||
} else if c.token != "" {
|
||||
tokenType := c.cfg.TokenType
|
||||
if tokenType == "" {
|
||||
tokenType = "Bearer"
|
||||
}
|
||||
req.Header.Set("Authorization", tokenType+" "+c.token)
|
||||
}
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
responseBody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes+1))
|
||||
if err != nil {
|
||||
return nil, resp.StatusCode, err
|
||||
}
|
||||
if len(responseBody) > maxResponseBytes {
|
||||
return nil, resp.StatusCode, fmt.Errorf("response exceeds %d MiB", maxResponseBytes/(1<<20))
|
||||
}
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return responseBody, resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: responseBody}
|
||||
}
|
||||
return responseBody, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
body := strings.TrimSpace(string(e.Body))
|
||||
if body == "" {
|
||||
return fmt.Sprintf("HTTP %d", e.StatusCode)
|
||||
}
|
||||
var payload struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if json.Unmarshal(e.Body, &payload) == nil {
|
||||
for _, msg := range []string{payload.ErrorDescription, payload.Message, payload.Error} {
|
||||
if msg != "" {
|
||||
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(body) > 500 {
|
||||
body = body[:500] + "…"
|
||||
}
|
||||
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, body)
|
||||
}
|
||||
|
||||
func (c *Client) get(ctx context.Context, endpoint string) ([]byte, error) {
|
||||
body, _, err := c.request(ctx, http.MethodGet, endpoint, nil, "")
|
||||
return body, err
|
||||
}
|
||||
|
||||
func (c *Client) postJSON(ctx context.Context, endpoint string, value any) ([]byte, error) {
|
||||
body, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.postBody(ctx, endpoint, body, "application/json")
|
||||
}
|
||||
|
||||
func (c *Client) postBody(ctx context.Context, endpoint string, body []byte, contentType string) ([]byte, error) {
|
||||
response, _, err := c.request(ctx, http.MethodPost, endpoint, body, contentType)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (c *Client) putBody(ctx context.Context, endpoint string, body []byte, contentType string) ([]byte, error) {
|
||||
response, _, err := c.request(ctx, http.MethodPut, endpoint, body, contentType)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (c *Client) patchBody(ctx context.Context, endpoint string, body []byte, contentType string) ([]byte, error) {
|
||||
response, _, err := c.request(ctx, http.MethodPatch, endpoint, body, contentType)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (c *Client) delete(ctx context.Context, endpoint string) ([]byte, error) {
|
||||
response, _, err := c.request(ctx, http.MethodDelete, endpoint, nil, "")
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (c *Client) deleteBody(ctx context.Context, endpoint string, body []byte, contentType string) ([]byte, error) {
|
||||
response, _, err := c.request(ctx, http.MethodDelete, endpoint, body, contentType)
|
||||
return response, err
|
||||
}
|
||||
Reference in new issue
Block a user