github.com/decred/politeia@v1.4.0/politeiawww/cmd/pictl/cmdproposalsetstatus.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"encoding/hex"
     9  	"fmt"
    10  	"strconv"
    11  
    12  	rcv1 "github.com/decred/politeia/politeiawww/api/records/v1"
    13  	pclient "github.com/decred/politeia/politeiawww/client"
    14  	"github.com/decred/politeia/politeiawww/cmd/shared"
    15  )
    16  
    17  // cmdProposalSetStatus sets the status of a proposal.
    18  type cmdProposalSetStatus struct {
    19  	Args struct {
    20  		Token   string `positional-arg-name:"token" required:"true"`
    21  		Status  string `positional-arg-name:"status" required:"true"`
    22  		Reason  string `positional-arg-name:"reason"`
    23  		Version uint32 `positional-arg-name:"version"`
    24  	} `positional-args:"true"`
    25  }
    26  
    27  // Execute executes the cmdProposalSetStatus command.
    28  //
    29  // This function satisfies the go-flags Commander interface.
    30  func (c *cmdProposalSetStatus) Execute(args []string) error {
    31  	_, err := proposalSetStatus(c)
    32  	if err != nil {
    33  		return err
    34  	}
    35  	return nil
    36  }
    37  
    38  // proposalSetStatus sets the status of a proposal. This function has been
    39  // pulled out of the Execute method so that is can be used in the test
    40  // commands.
    41  func proposalSetStatus(c *cmdProposalSetStatus) (*rcv1.Record, error) {
    42  	// Verify user identity. This will be needed to sign the status
    43  	// change.
    44  	if cfg.Identity == nil {
    45  		return nil, shared.ErrUserIdentityNotFound
    46  	}
    47  
    48  	// Setup client
    49  	opts := pclient.Opts{
    50  		HTTPSCert:  cfg.HTTPSCert,
    51  		Cookies:    cfg.Cookies,
    52  		HeaderCSRF: cfg.CSRF,
    53  		Verbose:    cfg.Verbose,
    54  		RawJSON:    cfg.RawJSON,
    55  	}
    56  	pc, err := pclient.New(cfg.Host, opts)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	// Parse status. This can be either the numeric status code or the
    62  	// human readable equivalent.
    63  	status, err := parseRecordStatus(c.Args.Status)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	// Setup version
    69  	var version uint32
    70  	if c.Args.Version != 0 {
    71  		version = c.Args.Version
    72  	} else {
    73  		// Get the version manually
    74  		d := rcv1.Details{
    75  			Token: c.Args.Token,
    76  		}
    77  		r, err := pc.RecordDetails(d)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  		version = r.Version
    82  	}
    83  
    84  	// Setup request
    85  	msg := c.Args.Token + strconv.FormatUint(uint64(version), 10) +
    86  		strconv.Itoa(int(status)) + c.Args.Reason
    87  	sig := cfg.Identity.SignMessage([]byte(msg))
    88  	ss := rcv1.SetStatus{
    89  		Token:     c.Args.Token,
    90  		Version:   version,
    91  		Status:    status,
    92  		Reason:    c.Args.Reason,
    93  		PublicKey: cfg.Identity.Public.String(),
    94  		Signature: hex.EncodeToString(sig[:]),
    95  	}
    96  
    97  	// Send request
    98  	ssr, err := pc.RecordSetStatus(ss)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	// Verify record
   104  	vr, err := client.Version()
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	err = pclient.RecordVerify(ssr.Record, vr.PubKey)
   109  	if err != nil {
   110  		return nil, fmt.Errorf("unable to verify record: %v", err)
   111  	}
   112  
   113  	// Print proposal to stdout
   114  	err = printProposal(ssr.Record)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return &ssr.Record, nil
   120  }
   121  
   122  func parseRecordState(state string) (rcv1.RecordStateT, error) {
   123  	// Parse status. This can be either the numeric status code or the
   124  	// human readable equivalent.
   125  	var (
   126  		rc rcv1.RecordStateT
   127  
   128  		states = map[string]rcv1.RecordStateT{
   129  			"unvetted": rcv1.RecordStateUnvetted,
   130  			"vetted":   rcv1.RecordStateVetted,
   131  		}
   132  	)
   133  	u, err := strconv.ParseUint(state, 10, 32)
   134  	if err == nil {
   135  		// Numeric state code found
   136  		rc = rcv1.RecordStateT(u)
   137  	} else if s, ok := states[state]; ok {
   138  		// Human readable state code found
   139  		rc = s
   140  	} else {
   141  		return rc, fmt.Errorf("invalid state '%v'", state)
   142  	}
   143  
   144  	return rc, nil
   145  }
   146  
   147  func parseRecordStatus(status string) (rcv1.RecordStatusT, error) {
   148  	// Parse status. This can be either the numeric status code or the
   149  	// human readable equivalent.
   150  	var (
   151  		rc rcv1.RecordStatusT
   152  
   153  		statuses = map[string]rcv1.RecordStatusT{
   154  			"public":    rcv1.RecordStatusPublic,
   155  			"censor":    rcv1.RecordStatusCensored,
   156  			"censored":  rcv1.RecordStatusCensored,
   157  			"abandon":   rcv1.RecordStatusArchived,
   158  			"abandoned": rcv1.RecordStatusArchived,
   159  			"archive":   rcv1.RecordStatusArchived,
   160  			"archived":  rcv1.RecordStatusArchived,
   161  		}
   162  	)
   163  	u, err := strconv.ParseUint(status, 10, 32)
   164  	if err == nil {
   165  		// Numeric status code found
   166  		rc = rcv1.RecordStatusT(u)
   167  	} else if s, ok := statuses[status]; ok {
   168  		// Human readable status code found
   169  		rc = s
   170  	} else {
   171  		return rc, fmt.Errorf("invalid status '%v'", status)
   172  	}
   173  
   174  	return rc, nil
   175  }
   176  
   177  // proposalSetStatusHelpMsg is printed to stdout by the help command.
   178  const proposalSetStatusHelpMsg = `proposalsetstatus "token" "status" "reason"
   179  
   180  Set the status of a proposal. This command assumes the proposal is a vetted
   181  record. If the proposal is unvetted, the --unvetted flag must be used. Requires
   182  admin priviledges.
   183  
   184  Valid statuses:
   185    public
   186    censor
   187    abandon
   188  
   189  The following statuses require a status change reason to be included:
   190    censor
   191    abandon
   192  
   193  Arguments:
   194  1. token   (string, required)  Proposal censorship token
   195  2. status  (string, required)  New status
   196  3. reason  (string, optional)  Status change reason
   197  4. version (string, optional)  Proposal version. This will be retrieved from
   198                                 the backend if one is not provided.
   199  `