github.com/decred/politeia@v1.4.0/politeiawww/cmd/cmswww/editinvoice.go (about)

     1  // Copyright (c) 2017-2019 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  	"bufio"
     9  	"encoding/base64"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/decred/politeia/politeiad/api/v1/mime"
    19  	v1 "github.com/decred/politeia/politeiawww/api/cms/v1"
    20  	www "github.com/decred/politeia/politeiawww/api/www/v1"
    21  	"github.com/decred/politeia/politeiawww/cmd/shared"
    22  	"github.com/decred/politeia/util"
    23  )
    24  
    25  // EditInvoiceCmd edits an existing invoice.
    26  type EditInvoiceCmd struct {
    27  	Args struct {
    28  		Month       uint     `positional-arg-name:"month" required:"true"`
    29  		Year        uint     `positional-arg-name:"year"`
    30  		Token       string   `positional-arg-name:"token"`           // Censorship token
    31  		CSV         string   `positional-arg-name:"csvfile"`         // Invoice CSV file
    32  		Attachments []string `positional-arg-name:"attachmentfiles"` // Invoice attachments
    33  	} `positional-args:"true" optional:"true"`
    34  	Name           string `long:"name" optional:"true" description:"Full name of the contractor"`
    35  	Contact        string `long:"contact" optional:"true" description:"Email address or contact of the contractor"`
    36  	Location       string `long:"location" optional:"true" description:"Location (e.g. Dallas, TX, USA) of the contractor"`
    37  	PaymentAddress string `long:"paymentaddress" optional:"true" description:"Payment address for this invoice."`
    38  	Rate           string `long:"rate" optional:"true" description:"Hourly rate for labor."`
    39  }
    40  
    41  // Execute executes the edit invoice command.
    42  func (cmd *EditInvoiceCmd) Execute(args []string) error {
    43  	month := cmd.Args.Month
    44  	year := cmd.Args.Year
    45  	token := cmd.Args.Token
    46  	csvFile := cmd.Args.CSV
    47  	attachmentFiles := cmd.Args.Attachments
    48  
    49  	if csvFile == "" {
    50  		return errInvoiceCSVNotFound
    51  	}
    52  
    53  	// Check for user identity
    54  	if cfg.Identity == nil {
    55  		return shared.ErrUserIdentityNotFound
    56  	}
    57  
    58  	// Get server public key
    59  	vr, err := client.Version()
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	if cmd.Name == "" || cmd.Location == "" || cmd.PaymentAddress == "" {
    65  		reader := bufio.NewReader(os.Stdin)
    66  		if cmd.Name == "" {
    67  			fmt.Print("Enter name for the invoice: ")
    68  			cmd.Name, _ = reader.ReadString('\n')
    69  		}
    70  		if cmd.Contact == "" {
    71  			fmt.Print("Enter email to associate with this invoice: ")
    72  			cmd.Contact, _ = reader.ReadString('\n')
    73  		}
    74  		if cmd.Location == "" {
    75  			fmt.Print("Enter location to associate with this invoice: ")
    76  			cmd.Location, _ = reader.ReadString('\n')
    77  		}
    78  		if cmd.PaymentAddress == "" {
    79  			fmt.Print("Enter payment address for this invoice: ")
    80  			cmd.PaymentAddress, _ = reader.ReadString('\n')
    81  		}
    82  		if cmd.Rate == "" {
    83  			fmt.Print("Enter hourly rate for this invoice: ")
    84  			cmd.Rate, _ = reader.ReadString('\n')
    85  		}
    86  		fmt.Print("\nPlease carefully review your information and ensure it's " +
    87  			"correct. If not, press Ctrl + C to exit. Or, press Enter to continue " +
    88  			"your registration.")
    89  		reader.ReadString('\n')
    90  	}
    91  
    92  	var csv []byte
    93  	files := make([]www.File, 0, www.PolicyMaxImages+1)
    94  	// Read markdown file into memory and convert to type File
    95  	fpath := util.CleanAndExpandPath(csvFile)
    96  	csv, err = os.ReadFile(fpath)
    97  	if err != nil {
    98  		return fmt.Errorf("ReadFile %v: %v", fpath, err)
    99  	}
   100  
   101  	invInput, err := validateParseCSV(csv)
   102  	if err != nil {
   103  		return fmt.Errorf("Parsing CSV failed: %v", err)
   104  	}
   105  
   106  	invInput.Month = month
   107  	invInput.Year = year
   108  	invInput.ContractorName = strings.TrimSpace(cmd.Name)
   109  	invInput.ContractorLocation = strings.TrimSpace(cmd.Location)
   110  	invInput.ContractorContact = strings.TrimSpace(cmd.Contact)
   111  	invInput.PaymentAddress = strings.TrimSpace(cmd.PaymentAddress)
   112  	invInput.Version = v1.InvoiceInputVersion
   113  
   114  	rate, err := strconv.Atoi(strings.TrimSpace(cmd.Rate))
   115  	if err != nil {
   116  		return fmt.Errorf("invalid rate entered, please try again")
   117  	}
   118  	invInput.ContractorRate = uint(rate)
   119  
   120  	b, err := json.Marshal(invInput)
   121  	if err != nil {
   122  		return fmt.Errorf("Marshal: %v", err)
   123  	}
   124  
   125  	f := www.File{
   126  		Name:    "invoice.json",
   127  		MIME:    mime.DetectMimeType(b),
   128  		Digest:  hex.EncodeToString(util.Digest(b)),
   129  		Payload: base64.StdEncoding.EncodeToString(b),
   130  	}
   131  
   132  	files = append(files, f)
   133  
   134  	// Read attachment files into memory and convert to type File
   135  	for _, file := range attachmentFiles {
   136  		path := util.CleanAndExpandPath(file)
   137  		attachment, err := os.ReadFile(path)
   138  		if err != nil {
   139  			return fmt.Errorf("ReadFile %v: %v", path, err)
   140  		}
   141  
   142  		f := www.File{
   143  			Name:    filepath.Base(file),
   144  			MIME:    mime.DetectMimeType(attachment),
   145  			Digest:  hex.EncodeToString(util.Digest(attachment)),
   146  			Payload: base64.StdEncoding.EncodeToString(attachment),
   147  		}
   148  
   149  		files = append(files, f)
   150  	}
   151  
   152  	// Compute merkle root and sign it
   153  	sig, err := signedMerkleRoot(files, nil, cfg.Identity)
   154  	if err != nil {
   155  		return fmt.Errorf("SignMerkleRoot: %v", err)
   156  	}
   157  
   158  	// Setup edit invoice request
   159  	ei := &v1.EditInvoice{
   160  		Token:     token,
   161  		Files:     files,
   162  		PublicKey: hex.EncodeToString(cfg.Identity.Public.Key[:]),
   163  		Signature: sig,
   164  	}
   165  
   166  	// Print request details
   167  	err = shared.PrintJSON(ei)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	// Send request
   173  	eir, err := client.EditInvoice(ei)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	// Verify the censorship record
   179  	pr := v1.InvoiceRecord{
   180  		Files:            ei.Files,
   181  		PublicKey:        ei.PublicKey,
   182  		Signature:        ei.Signature,
   183  		CensorshipRecord: eir.Invoice.CensorshipRecord,
   184  	}
   185  	err = verifyInvoice(pr, vr.PubKey)
   186  	if err != nil {
   187  		return fmt.Errorf("unable to verify invoice %v: %v",
   188  			eir.Invoice.CensorshipRecord.Token, err)
   189  	}
   190  
   191  	// Print response details
   192  	return shared.PrintJSON(eir)
   193  }
   194  
   195  // editInvoiceHelpMsg is the output of the help command when 'editinvoice'
   196  // is specified.
   197  const editInvoiceHelpMsg = `editinvoice [flags] "month" "year" token" "csvfile" "attachmentfiles" 
   198  
   199  Edit a invoice.
   200  
   201  Arguments:
   202  1. month             (uint, required)     Invoice Month
   203  2. year              (uint, required)     Invoice Year
   204  1. token             (string, required)   Invoice censorship token
   205  2. csvfile           (string, required)   Edited invoice 
   206  3. attachmentfiles   (string, optional)   Attachments 
   207  
   208  Flags:
   209    --name              (string, optional)   Fill in contractor name
   210    --contact           (string, optional)   Fill in email address or contact of the contractor
   211    --location          (string, optional)   Fill in contractor location (e.g. Dallas, TX, USA) of the contractor
   212    --paymentaddress    (string, optional)   Fill in payment address for this invoice.
   213    --rate              (string, optional)   Fill in contractor pay rate for labor.
   214  
   215  Request:
   216  {
   217    "month":  (uint)    Invoice Month
   218    "token":  (string)  Censorship token
   219      "files": [
   220        {
   221          "name":      (string)  Filename 
   222          "mime":      (string)  Mime type 
   223          "digest":    (string)  File digest 
   224          "payload":   (string)  File payload 
   225        }
   226      ],
   227    "publickey": (string)  Public key used to sign invoice
   228    "signature": (string)  Signature of the merkle root 
   229  }
   230  
   231  Response:
   232  {
   233    "invoice": {
   234      "month":         (uint16)       Month of invoice
   235      "year":          (uint16)       Year of invoice
   236      "state":         (PropStateT)   Current state of invoice
   237      "status":        (PropStatusT)  Current status of invoice
   238      "timestamp":     (int64)        Timestamp of last update of invoice
   239      "userid":        (string)       ID of user who submitted invoice
   240      "username":      (string)       Username of user who submitted invoice
   241      "publickey":     (string)       Public key used to sign invoice
   242      "signature":     (string)       Signature of merkle root
   243      "files": [
   244        {
   245          "name":      (string)       Filename 
   246          "mime":      (string)       Mime type 
   247          "digest":    (string)       File digest 
   248          "payload":   (string)       File payload 
   249        }
   250      ],
   251      "numcomments":   (uint)    Number of comments on the invoice
   252      "version": 		 (string)  Version of invoice
   253      "censorshiprecord": {	
   254        "token":       (string)  Censorship token
   255        "merkle":      (string)  Merkle root of invoice
   256        "signature":   (string)  Server side signature of []byte(Merkle+Token)
   257      }
   258    }
   259  }`