751 lines
23 KiB
Go
751 lines
23 KiB
Go
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])
|
|
}
|
|
}
|