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,750 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func requireID(args []string, usage string) (string, error) {
|
||||
if len(args) == 0 || args[0] == "" || strings.HasPrefix(args[0], "-") {
|
||||
return "", errors.New(usage)
|
||||
}
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
func readRequiredBody(stdin io.Reader, args []string, label string) ([]byte, error) {
|
||||
body, err := readInput(stdin, inputFlag(args), positionalWithoutInput(args))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", label, err)
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, fmt.Errorf("%s requires JSON input or a file", label)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func cmsPatchNode(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
id, err := requireID(args, "cms nodes patch requires a node id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := readRequiredBody(stdin, args[1:], "node patch")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("node patch input is not valid JSON")
|
||||
}
|
||||
response, err := client.patchBody(ctx, client.cmsURL("/nodes/"+escapePathSegment(id)), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func cmsDeleteNode(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
id, err := requireID(args, "cms nodes delete requires a node id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := flag.NewFlagSet("cms nodes delete", flag.ContinueOnError)
|
||||
confirmed := fs.Bool("yes", false, "confirm deletion")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !*confirmed {
|
||||
return errors.New("cms nodes delete requires --yes")
|
||||
}
|
||||
body, err := client.delete(ctx, client.cmsURL("/nodes/"+escapePathSegment(id)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
|
||||
func cmsRetypeNode(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
id, err := requireID(args, "cms nodes retype requires a node id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := flag.NewFlagSet("cms nodes retype", flag.ContinueOnError)
|
||||
dataType := fs.String("type", "", "new node type")
|
||||
input := fs.String("input", "", "complete request JSON file or - for stdin")
|
||||
data := fs.String("data", "", "replacement JSON data object, @file, or - for stdin")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := readInput(stdin, *input, fs.Args())
|
||||
if err != nil {
|
||||
return fmt.Errorf("read node retype: %w", err)
|
||||
}
|
||||
if len(body) == 0 {
|
||||
if *dataType == "" {
|
||||
return errors.New("cms nodes retype requires --type when --input is not used")
|
||||
}
|
||||
var dataObject map[string]any
|
||||
if *data != "" {
|
||||
dataBody, readErr := readJSONValue(stdin, *data)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if err := json.Unmarshal(dataBody, &dataObject); err != nil {
|
||||
return fmt.Errorf("--data must be a JSON object: %w", err)
|
||||
}
|
||||
}
|
||||
body, err = json.Marshal(map[string]any{"dataType": *dataType, "data": dataObject})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("node retype input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.cmsURL("/nodes/"+escapePathSegment(id)+"/retype"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func runCMSEdges(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
if len(args) == 0 || args[0] == "help" || args[0] == "--help" {
|
||||
_, _ = io.WriteString(stdout, "Usage: cmsctl cms edges get <node-id>\n cmsctl cms edges create [--from ID --to ID --type child --handle NAME --sort N]\n cmsctl cms edges delete <edge-id> --yes\n cmsctl cms edges reorder <parent-id> --order edge-id,...\n")
|
||||
return nil
|
||||
}
|
||||
switch args[0] {
|
||||
case "get":
|
||||
id, err := requireID(args[1:], "cms edges get requires a node id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := client.get(ctx, client.cmsURL("/nodes/"+escapePathSegment(id)+"/edges"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
case "create":
|
||||
fs := flag.NewFlagSet("cms edges create", flag.ContinueOnError)
|
||||
from := fs.String("from", "", "source node id")
|
||||
to := fs.String("to", "", "target node id")
|
||||
typ := fs.String("type", "child", "edge type")
|
||||
handle := fs.String("handle", "", "connection handle")
|
||||
sort := fs.Int("sort", 0, "sibling sort position")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := readInput(stdin, inputFlag(args[1:]), fs.Args())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
if *from == "" || *to == "" {
|
||||
return errors.New("cms edges create requires --from and --to, or --input")
|
||||
}
|
||||
body, err = json.Marshal(map[string]any{"from": *from, "to": *to, "type": *typ, "handle": *handle, "sort": *sort})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("edge input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.cmsURL("/edges"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
case "delete":
|
||||
id, err := requireID(args[1:], "cms edges delete requires an edge id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := flag.NewFlagSet("cms edges delete", flag.ContinueOnError)
|
||||
confirmed := fs.Bool("yes", false, "confirm deletion")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[2:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !*confirmed {
|
||||
return errors.New("cms edges delete requires --yes")
|
||||
}
|
||||
body, err := client.delete(ctx, client.cmsURL("/edges/"+escapePathSegment(id)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
case "reorder":
|
||||
parent, err := requireID(args[1:], "cms edges reorder requires a parent node id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := flag.NewFlagSet("cms edges reorder", flag.ContinueOnError)
|
||||
order := fs.String("order", "", "comma-separated edge ids in the desired order")
|
||||
input := fs.String("input", "", "complete request JSON file or - for stdin")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[2:]); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := readInput(stdin, *input, fs.Args())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
if *order == "" {
|
||||
return errors.New("cms edges reorder requires --order or --input")
|
||||
}
|
||||
body, err = json.Marshal(map[string]any{"parent": parent, "order": splitCSV(*order)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("edge reorder input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.cmsURL("/edges/reorder"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
default:
|
||||
return fmt.Errorf("unknown cms edges command %q", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func runCMSSchemas(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
if len(args) == 0 || args[0] == "list" {
|
||||
body, err := client.get(ctx, client.cmsURL("/schemas"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
typ, err := requireID(args[1:], "cms schemas requires a schema type")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch args[0] {
|
||||
case "get":
|
||||
body, err := client.get(ctx, client.cmsURL("/schemas/"+escapePathSegment(typ)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
case "put":
|
||||
body, err := readRequiredBody(stdin, args[2:], "schema")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("schema input is not valid JSON")
|
||||
}
|
||||
response, err := client.putBody(ctx, client.cmsURL("/schemas/"+escapePathSegment(typ)), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
case "delete":
|
||||
fs := flag.NewFlagSet("cms schemas delete", flag.ContinueOnError)
|
||||
confirmed := fs.Bool("yes", false, "confirm deletion")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[2:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !*confirmed {
|
||||
return errors.New("cms schemas delete requires --yes")
|
||||
}
|
||||
body, err := client.delete(ctx, client.cmsURL("/schemas/"+escapePathSegment(typ)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
default:
|
||||
return fmt.Errorf("unknown cms schemas command %q", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func runCMSTemplates(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
if len(args) == 0 || args[0] == "list" {
|
||||
body, err := client.get(ctx, client.cmsURL("/templates"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
name, err := requireID(args[1:], "cms templates requires a template name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch args[0] {
|
||||
case "get":
|
||||
body, err := client.get(ctx, client.cmsURL("/templates/"+escapePathSegment(name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
case "put":
|
||||
body, err := readRequiredBody(stdin, args[2:], "template")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response, err := client.putBody(ctx, client.cmsURL("/templates/"+escapePathSegment(name)), body, "text/plain; charset=utf-8")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
case "delete":
|
||||
fs := flag.NewFlagSet("cms templates delete", flag.ContinueOnError)
|
||||
confirmed := fs.Bool("yes", false, "confirm deletion")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[2:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !*confirmed {
|
||||
return errors.New("cms templates delete requires --yes")
|
||||
}
|
||||
body, err := client.delete(ctx, client.cmsURL("/templates/"+escapePathSegment(name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
default:
|
||||
return fmt.Errorf("unknown cms templates command %q", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func runCMSBlueprints(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
if len(args) == 0 || args[0] == "list" {
|
||||
body, err := client.get(ctx, client.cmsURL("/blueprints"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
if args[0] != "create" {
|
||||
return fmt.Errorf("unknown cms blueprints command %q", args[0])
|
||||
}
|
||||
fs := flag.NewFlagSet("cms blueprints create", flag.ContinueOnError)
|
||||
blueprintID := fs.String("blueprint", "", "blueprint id")
|
||||
name := fs.String("name", "", "created node name")
|
||||
pathValue := fs.String("path", "", "created node path")
|
||||
input := fs.String("input", "", "complete request JSON file or - for stdin")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := readInput(stdin, *input, fs.Args())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
if *blueprintID == "" {
|
||||
return errors.New("cms blueprints create requires --blueprint or --input")
|
||||
}
|
||||
body, err = json.Marshal(map[string]string{"blueprintId": *blueprintID, "name": *name, "path": *pathValue})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("blueprint input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.cmsURL("/nodes/create-from-blueprint"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func runCMSTree(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
id, err := requireID(args, "cms tree requires a root node id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := flag.NewFlagSet("cms tree", flag.ContinueOnError)
|
||||
depth := fs.Int("depth", -1, "tree depth; -1 is unlimited")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
query := url.Values{"depth": {strconv.Itoa(*depth)}}
|
||||
body, err := client.get(ctx, client.cmsURL("/tree/"+escapePathSegment(id))+"?"+query.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
|
||||
func runCMSNav(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("cms nav", flag.ContinueOnError)
|
||||
typ := fs.String("type", "", "navigation edge type, default tree")
|
||||
dataType := fs.String("data-type", "", "restrict forest to a data type")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
pathValue := "/nav"
|
||||
if len(fs.Args()) > 0 {
|
||||
pathValue += "/" + escapePathSegment(fs.Args()[0])
|
||||
}
|
||||
query := url.Values{}
|
||||
if *typ != "" {
|
||||
query.Set("type", *typ)
|
||||
}
|
||||
if *dataType != "" {
|
||||
query.Set("dataType", *dataType)
|
||||
}
|
||||
endpoint := client.cmsURL(pathValue)
|
||||
if len(query) > 0 {
|
||||
endpoint += "?" + query.Encode()
|
||||
}
|
||||
body, err := client.get(ctx, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
|
||||
func runCMSGraph(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("cms graph", flag.ContinueOnError)
|
||||
root := fs.String("root", "", "root node id")
|
||||
budget := fs.Int("budget", 0, "maximum node count")
|
||||
edges := fs.String("edges", "", "comma-separated edge types")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
query := url.Values{}
|
||||
if *root != "" {
|
||||
query.Set("root", *root)
|
||||
}
|
||||
if *budget > 0 {
|
||||
query.Set("budget", strconv.Itoa(*budget))
|
||||
}
|
||||
if *edges != "" {
|
||||
query["edges"] = splitCSV(*edges)
|
||||
}
|
||||
endpoint := client.cmsURL("/graph")
|
||||
if len(query) > 0 {
|
||||
endpoint += "?" + query.Encode()
|
||||
}
|
||||
body, err := client.get(ctx, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
|
||||
func runCMSPublish(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
body, err := readInput(stdin, inputFlag(args), positionalWithoutInput(args))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
ids := positionalWithoutInput(args)
|
||||
if len(ids) == 0 {
|
||||
return errors.New("cms publish requires root ids or --input")
|
||||
}
|
||||
body, err = json.Marshal(map[string]any{"ids": ids})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("publish input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.cmsURL("/publish-roots"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func runCMSRegenerate(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("cms regenerate", flag.ContinueOnError)
|
||||
typ := fs.String("type", "", "only regenerate roots affected by this node type")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint := client.cmsURL("/regenerate")
|
||||
if *typ != "" {
|
||||
endpoint += "?type=" + url.QueryEscape(*typ)
|
||||
}
|
||||
body, err := client.postBody(ctx, endpoint, nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
|
||||
func runCMSScript(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("cms script", flag.ContinueOnError)
|
||||
script := fs.String("script", "", "JavaScript source")
|
||||
input := fs.String("input", "", "complete request JSON file or - for stdin")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := readInput(stdin, *input, fs.Args())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
if *script == "" {
|
||||
return errors.New("cms script requires --script or --input")
|
||||
}
|
||||
body, err = json.Marshal(map[string]string{"script": *script})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("script input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.cmsURL("/scripts/run"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func runFinderFacets(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("finder facets", flag.ContinueOnError)
|
||||
q := fs.String("q", "", "search query")
|
||||
filters := fs.String("filters", "", "JSON-encoded filters")
|
||||
resultKey := fs.String("result-key", "", "cached search result key")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
query := url.Values{}
|
||||
if *q != "" {
|
||||
query.Set("q", *q)
|
||||
}
|
||||
if *filters != "" {
|
||||
query.Set("filters", *filters)
|
||||
}
|
||||
if *resultKey != "" {
|
||||
query.Set("resultKey", *resultKey)
|
||||
}
|
||||
return finderGetQuery(ctx, client, "facets", query, stdout)
|
||||
}
|
||||
|
||||
func runFinderConfig(ctx context.Context, client *Client, args []string, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("finder config", flag.ContinueOnError)
|
||||
filters := fs.String("filters", "", "JSON-encoded filters")
|
||||
stock := fs.String("stock", "", "stock filter")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
query := url.Values{}
|
||||
if *filters != "" {
|
||||
query.Set("filters", *filters)
|
||||
}
|
||||
if *stock != "" {
|
||||
query.Set("stock", *stock)
|
||||
}
|
||||
return finderGetQuery(ctx, client, "config", query, stdout)
|
||||
}
|
||||
|
||||
func runFinderRelated(ctx context.Context, client *Client, endpoint string, args []string, stdout io.Writer) error {
|
||||
id, err := requireID(args, "finder "+endpoint+" requires an item id or SKU")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return finderSimpleGet(ctx, client, []string{id}, endpoint, stdout)
|
||||
}
|
||||
|
||||
func runFinderCompatible(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
id, err := requireID(args, "finder compatible requires an item id or SKU")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := flag.NewFlagSet("finder compatible", flag.ContinueOnError)
|
||||
limit := fs.Int("limit", 60, "maximum results")
|
||||
input := fs.String("cart-input", "", "JSON array of cart item IDs, or - for stdin")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
query := url.Values{"limit": {strconv.Itoa(*limit)}}
|
||||
endpoint := client.finderURL("/compatible/"+escapePathSegment(id)) + "?" + query.Encode()
|
||||
if *input == "" {
|
||||
body, err := client.get(ctx, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
body, err := readInput(stdin, *input, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("compatible cart input is not valid JSON")
|
||||
}
|
||||
response, err := client.postBody(ctx, endpoint, body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func runFinderStreamItems(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
body, err := readRequiredBody(stdin, args, "finder stream-items")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ids []string
|
||||
for _, line := range strings.Split(string(body), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := strconv.ParseUint(line, 10, 32); err != nil {
|
||||
return fmt.Errorf("finder stream-items: invalid item id %q", line)
|
||||
}
|
||||
ids = append(ids, line)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return errors.New("finder stream-items input contains no item ids")
|
||||
}
|
||||
response, err := client.postBody(ctx, client.finderURL("/stream-items"), []byte(strings.Join(ids, "\n")+"\n"), "text/plain")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
|
||||
func runFinderSaveTrigger(ctx context.Context, client *Client, stdout io.Writer) error {
|
||||
body, err := client.postBody(ctx, client.finderURL("/save-trigger"), nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
|
||||
func runFinderAdmin(ctx context.Context, client *Client, args []string, stdin io.Reader, stdout io.Writer) error {
|
||||
if len(args) == 0 || args[0] == "help" || args[0] == "--help" {
|
||||
_, _ = io.WriteString(stdout, "Usage: cmsctl finder admin settings {get|put}\n cmsctl finder admin facets {list|create|update|delete}\n cmsctl finder admin update-by-filter [file|-]\n cmsctl finder admin delete-by-filter [file|-] --yes\n")
|
||||
return nil
|
||||
}
|
||||
switch args[0] {
|
||||
case "settings":
|
||||
if len(args) < 2 || args[1] == "get" {
|
||||
body, err := client.get(ctx, client.finderAdminURL("/settings"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
if args[1] != "put" {
|
||||
return fmt.Errorf("unknown finder admin settings command %q", args[1])
|
||||
}
|
||||
body, err := readRequiredBody(stdin, args[2:], "finder settings")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("finder settings input is not valid JSON")
|
||||
}
|
||||
response, err := client.putBody(ctx, client.finderAdminURL("/settings"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
case "facets":
|
||||
if len(args) < 2 || args[1] == "list" {
|
||||
body, err := client.get(ctx, client.finderAdminURL("/facets"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
}
|
||||
id, err := requireID(args[2:], "finder admin facets requires a facet id")
|
||||
if err != nil && args[1] != "create" {
|
||||
return err
|
||||
}
|
||||
switch args[1] {
|
||||
case "create":
|
||||
body, readErr := readRequiredBody(stdin, args[2:], "finder facet")
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
response, reqErr := client.postBody(ctx, client.finderAdminURL("/facets"), body, "application/json")
|
||||
if reqErr != nil {
|
||||
return reqErr
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
case "update":
|
||||
body, readErr := readRequiredBody(stdin, args[3:], "finder facet")
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
response, reqErr := client.putBody(ctx, client.finderAdminURL("/facets/"+escapePathSegment(id)), body, "application/json")
|
||||
if reqErr != nil {
|
||||
return reqErr
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
case "delete":
|
||||
fs := flag.NewFlagSet("finder admin facets delete", flag.ContinueOnError)
|
||||
confirmed := fs.Bool("yes", false, "confirm deletion")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[3:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !*confirmed {
|
||||
return errors.New("finder admin facets delete requires --yes")
|
||||
}
|
||||
body, reqErr := client.delete(ctx, client.finderAdminURL("/facets/"+escapePathSegment(id)))
|
||||
if reqErr != nil {
|
||||
return reqErr
|
||||
}
|
||||
return writeRaw(stdout, body)
|
||||
default:
|
||||
return fmt.Errorf("unknown finder admin facets command %q", args[1])
|
||||
}
|
||||
case "update-by-filter", "delete-by-filter":
|
||||
body, err := readRequiredBody(stdin, args[1:], "finder "+args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !json.Valid(body) {
|
||||
return errors.New("finder filter input is not valid JSON")
|
||||
}
|
||||
if args[0] == "delete-by-filter" {
|
||||
fs := flag.NewFlagSet("finder admin delete-by-filter", flag.ContinueOnError)
|
||||
confirmed := fs.Bool("yes", false, "confirm deletion")
|
||||
fs.SetOutput(io.Discard)
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !*confirmed {
|
||||
return errors.New("finder admin delete-by-filter requires --yes")
|
||||
}
|
||||
response, reqErr := client.deleteBody(ctx, client.finderAdminURL("/items/by-filter"), body, "application/json")
|
||||
if reqErr != nil {
|
||||
return reqErr
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
}
|
||||
response, err := client.postBody(ctx, client.finderAdminURL("/items/batch/by-filter"), body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRaw(stdout, response)
|
||||
default:
|
||||
return fmt.Errorf("unknown finder admin command %q", args[0])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user