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 `