- add batch-scoped reconcile_runs persistence and queries - route batch detail and reconcile writes through batch_id/host_id - refresh production boards with host-scope acceptance artifacts - include latest real-host acceptance evidence for self_service and subscription
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package sub2api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
func (c *Client) ProbeCapabilities(ctx context.Context) (HostCapabilities, error) {
|
|
groups, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/groups", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
channels, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/channels", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
plans, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/payment/plans", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
accounts, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/accounts", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
accountTest, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/accounts/__probe__/test", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
accountModels, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/accounts/__probe__/models", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
subscriptions, err := c.probeEndpoint(ctx, http.MethodGet, "/api/v1/admin/subscriptions/assign", nil)
|
|
if err != nil {
|
|
return HostCapabilities{}, err
|
|
}
|
|
|
|
return HostCapabilities{
|
|
Groups: groups,
|
|
Channels: channels,
|
|
Plans: plans,
|
|
Accounts: accounts,
|
|
AccountTest: accountTest,
|
|
AccountModels: accountModels,
|
|
Subscriptions: subscriptions,
|
|
}, nil
|
|
}
|
|
|
|
func (c *Client) probeEndpoint(ctx context.Context, method, path string, requestBody any) (bool, error) {
|
|
statusCode, _, body, err := c.perform(ctx, method, path, requestBody)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
switch {
|
|
case statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices:
|
|
return true, nil
|
|
case statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden:
|
|
return false, newHTTPError(method, path, statusCode, body)
|
|
case statusCode == http.StatusNotFound || statusCode == http.StatusMethodNotAllowed:
|
|
return false, nil
|
|
case statusCode >= http.StatusBadRequest && statusCode < http.StatusInternalServerError:
|
|
return true, nil
|
|
default:
|
|
return false, newHTTPError(method, path, statusCode, body)
|
|
}
|
|
}
|
|
|
|
func looksLikeExistingEndpoint(headers http.Header, body []byte) bool {
|
|
contentType := strings.ToLower(headers.Get("Content-Type"))
|
|
trimmedBody := bytes.TrimSpace(body)
|
|
|
|
if strings.Contains(contentType, "application/json") || strings.Contains(contentType, "text/event-stream") {
|
|
return true
|
|
}
|
|
if len(trimmedBody) == 0 {
|
|
return false
|
|
}
|
|
if trimmedBody[0] == '{' || trimmedBody[0] == '[' {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|