github.com/decred/politeia@v1.4.0/politeiawww/api/records/v1/v1.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 v1
     6  
     7  import "fmt"
     8  
     9  const (
    10  	// APIRoute is prefixed onto all routes defined in this package.
    11  	APIRoute = "/records/v1"
    12  
    13  	// RoutePolicy returns the policy for the records API.
    14  	RoutePolicy = "/policy"
    15  
    16  	// RouteNew adds a new record.
    17  	RouteNew = "/new"
    18  
    19  	// RouteEdit edits a record.
    20  	RouteEdit = "/edit"
    21  
    22  	// RouteSetStatus sets the status of a record.
    23  	RouteSetStatus = "/setstatus"
    24  
    25  	// RouteDetails returns the details of a record.
    26  	RouteDetails = "/details"
    27  
    28  	// RouteTimestamps returns the timestamps of a record.
    29  	RouteTimestamps = "/timestamps"
    30  
    31  	// RouteRecords returns a batch of records.
    32  	RouteRecords = "/records"
    33  
    34  	// RouteInventory returns the tokens of the records in the inventory,
    35  	// categorized by record state and record status.
    36  	RouteInventory = "/inventory"
    37  
    38  	// RouteInventoryOrdered returns a page of record tokens ordered by the
    39  	// timestamp of their most recent status change from newest to oldest.
    40  	RouteInventoryOrdered = "/inventoryordered"
    41  
    42  	// RouteUserRecords returnes the tokens of all records submitted by a user.
    43  	RouteUserRecords = "/userrecords"
    44  )
    45  
    46  // ErrorCodeT represents a user error code.
    47  type ErrorCodeT uint32
    48  
    49  const (
    50  	// ErrorCodeInvalid is an invalid error code.
    51  	ErrorCodeInvalid ErrorCodeT = 0
    52  
    53  	// ErrorCodeInputInvalid is returned when there is an error
    54  	// while prasing a command payload.
    55  	ErrorCodeInputInvalid ErrorCodeT = 1
    56  
    57  	// ErrorCodeFilesEmpty is returned when record's files are
    58  	// empty.
    59  	ErrorCodeFilesEmpty ErrorCodeT = 2
    60  
    61  	// ErrorCodeFileNameInvalid is returned when a file name is
    62  	// invalid.
    63  	ErrorCodeFileNameInvalid ErrorCodeT = 3
    64  
    65  	// ErrorCodeFileNameDuplicate is returned when a file name is
    66  	// a duplicate.
    67  	ErrorCodeFileNameDuplicate ErrorCodeT = 4
    68  
    69  	// ErrorCodeFileMIMETypeInvalid is returned when a file MIME type
    70  	// is invalid.
    71  	ErrorCodeFileMIMETypeInvalid ErrorCodeT = 5
    72  
    73  	// ErrorCodeFileMIMETypeUnsupported is returned when a file MIME
    74  	// type is unsupported.
    75  	ErrorCodeFileMIMETypeUnsupported ErrorCodeT = 6
    76  
    77  	// ErrorCodeFileDigestInvalid is returned when an invalid file
    78  	// digest found.
    79  	ErrorCodeFileDigestInvalid ErrorCodeT = 7
    80  
    81  	// ErrorCodeFilePayloadInvalid is returned when an invalid file
    82  	// payload found.
    83  	ErrorCodeFilePayloadInvalid ErrorCodeT = 8
    84  
    85  	// ErrorCodeMetadataStreamIDInvalid is returned a metadata stream
    86  	// ID is invalid.
    87  	ErrorCodeMetadataStreamIDInvalid ErrorCodeT = 9
    88  
    89  	// ErrorCodePublicKeyInvalid is returned when a public key is not
    90  	// a valid hex encoded, Ed25519 public key.
    91  	ErrorCodePublicKeyInvalid ErrorCodeT = 10
    92  
    93  	// ErrorCodeSignatureInvalid is returned when a signature is not
    94  	// a valid hex encoded, Ed25519 signature or when the signature is
    95  	// wrong.
    96  	ErrorCodeSignatureInvalid ErrorCodeT = 11
    97  
    98  	// ErrorCodeRecordTokenInvalid is returned when a record token is
    99  	// invalid.
   100  	ErrorCodeRecordTokenInvalid ErrorCodeT = 12
   101  
   102  	// ErrorCodeRecordNotFound is returned when a record is not found.
   103  	ErrorCodeRecordNotFound ErrorCodeT = 13
   104  
   105  	// ErrorCodeRecordLocked is returned when a record is locked.
   106  	ErrorCodeRecordLocked ErrorCodeT = 14
   107  
   108  	// ErrorCodeNoRecordChanges is retuned when no record changes found.
   109  	ErrorCodeNoRecordChanges ErrorCodeT = 15
   110  
   111  	// ErrorCodeRecordStateInvalid is returned when a record state is
   112  	// invalid.
   113  	ErrorCodeRecordStateInvalid ErrorCodeT = 16
   114  
   115  	// ErrorCodeRecordStatusInvalid is returned when a record status is
   116  	// invalid.
   117  	ErrorCodeRecordStatusInvalid ErrorCodeT = 17
   118  
   119  	// ErrorCodeStatusChangeInvalid is returned when a record status change
   120  	// is invalid.
   121  	ErrorCodeStatusChangeInvalid ErrorCodeT = 18
   122  
   123  	// ErrorCodeStatusReasonNotFound is returned when a record status change
   124  	// reason is not found.
   125  	ErrorCodeStatusReasonNotFound ErrorCodeT = 19
   126  
   127  	// ErrorCodePageSizeExceeded is returned when the request's page size
   128  	// exceeds the maximum page size of the request.
   129  	ErrorCodePageSizeExceeded ErrorCodeT = 20
   130  
   131  	// ErrorCodeLast is used by unit tests to verify that all error codes have
   132  	// a human readable entry in the ErrorCodes map. This error will never be
   133  	// returned.
   134  	ErrorCodeLast ErrorCodeT = 21
   135  )
   136  
   137  var (
   138  	// ErrorCodes contains the human readable errors.
   139  	ErrorCodes = map[ErrorCodeT]string{
   140  		ErrorCodeInvalid:                 "error invalid",
   141  		ErrorCodeInputInvalid:            "input invalid",
   142  		ErrorCodeFilesEmpty:              "files are empty",
   143  		ErrorCodeFileNameInvalid:         "file name invalid",
   144  		ErrorCodeFileNameDuplicate:       "file name duplicate",
   145  		ErrorCodeFileMIMETypeInvalid:     "file mime type invalid",
   146  		ErrorCodeFileMIMETypeUnsupported: "file mime type unsupported",
   147  		ErrorCodeFileDigestInvalid:       "file digest invalid",
   148  		ErrorCodeFilePayloadInvalid:      "file payload invalid",
   149  		ErrorCodeMetadataStreamIDInvalid: "metadata stream id invalid",
   150  		ErrorCodePublicKeyInvalid:        "public key invalid",
   151  		ErrorCodeSignatureInvalid:        "signature invalid",
   152  		ErrorCodeRecordTokenInvalid:      "record token invalid",
   153  		ErrorCodeRecordNotFound:          "record not found",
   154  		ErrorCodeRecordLocked:            "record locked",
   155  		ErrorCodeNoRecordChanges:         "no record changes",
   156  		ErrorCodeRecordStateInvalid:      "record state invalid",
   157  		ErrorCodeRecordStatusInvalid:     "record status invalid",
   158  		ErrorCodeStatusChangeInvalid:     "status change invalid",
   159  		ErrorCodeStatusReasonNotFound:    "status reason not found",
   160  		ErrorCodePageSizeExceeded:        "page size exceeded",
   161  	}
   162  )
   163  
   164  // UserErrorReply is the reply that the server returns when it encounters an
   165  // error that is caused by something that the user did (malformed input, bad
   166  // timing, etc). The HTTP status code will be 400.
   167  type UserErrorReply struct {
   168  	ErrorCode    ErrorCodeT `json:"errorcode"`
   169  	ErrorContext string     `json:"errorcontext,omitempty"`
   170  }
   171  
   172  // Error satisfies the error interface.
   173  func (e UserErrorReply) Error() string {
   174  	return fmt.Sprintf("user error code: %v", e.ErrorCode)
   175  }
   176  
   177  // PluginErrorReply is the reply that the server returns when it encounters
   178  // a plugin error.
   179  type PluginErrorReply struct {
   180  	PluginID     string `json:"pluginid"`
   181  	ErrorCode    uint32 `json:"errorcode"`
   182  	ErrorContext string `json:"errorcontext,omitempty"`
   183  }
   184  
   185  // Error satisfies the error interface.
   186  func (e PluginErrorReply) Error() string {
   187  	return fmt.Sprintf("plugin %v error code: %v", e.PluginID, e.ErrorCode)
   188  }
   189  
   190  // ServerErrorReply is the reply that the server returns when it encounters an
   191  // unrecoverable error while executing a command. The HTTP status code will be
   192  // 500 and the ErrorCode field will contain a UNIX timestamp that the user can
   193  // provide to the server admin to track down the error details in the logs.
   194  type ServerErrorReply struct {
   195  	ErrorCode int64 `json:"errorcode"`
   196  }
   197  
   198  // Error satisfies the error interface.
   199  func (e ServerErrorReply) Error() string {
   200  	return fmt.Sprintf("server error: %v", e.ErrorCode)
   201  }
   202  
   203  // Policy requests the policy settings for the records API.
   204  type Policy struct{}
   205  
   206  // PolicyReply is the reply to the Policy command.
   207  type PolicyReply struct {
   208  	RecordsPageSize   uint32 `json:"recordspagesize"`
   209  	InventoryPageSize uint32 `json:"inventorypagesize"`
   210  }
   211  
   212  // RecordStateT represents the state of a record.
   213  type RecordStateT uint32
   214  
   215  const (
   216  	// RecordStateInvalid is an invalid record state.
   217  	RecordStateInvalid RecordStateT = 0
   218  
   219  	// RecordStateUnvetted indicates a record has not been made public.
   220  	RecordStateUnvetted RecordStateT = 1
   221  
   222  	// RecordStateVetted indicates a record has been made public.
   223  	RecordStateVetted RecordStateT = 2
   224  
   225  	// RecordStateLast unit test only.
   226  	RecordStateLast RecordStateT = 3
   227  )
   228  
   229  var (
   230  	// RecordStates contains the human readable record states.
   231  	RecordStates = map[RecordStateT]string{
   232  		RecordStateInvalid:  "invalid",
   233  		RecordStateUnvetted: "unvetted",
   234  		RecordStateVetted:   "vetted",
   235  	}
   236  )
   237  
   238  // RecordStatusT represents the status of a record.
   239  type RecordStatusT uint32
   240  
   241  const (
   242  	// RecordStatusInvalid is an invalid status code.
   243  	RecordStatusInvalid RecordStatusT = 0
   244  
   245  	// RecordStatusUnreviewed indicates a record has not been made
   246  	// public yet. The state of an unreviewed record will always be
   247  	// unvetted.
   248  	RecordStatusUnreviewed RecordStatusT = 1
   249  
   250  	// RecordStatusPublic indicates a record has been made public. The
   251  	// state of a public record will always be vetted.
   252  	RecordStatusPublic RecordStatusT = 2
   253  
   254  	// RecordStatusCensored indicates a record has been censored. A
   255  	// censored record is locked from any further updates and all
   256  	// record content is permanently deleted. A censored record can
   257  	// have a state of either unvetted or vetted.
   258  	RecordStatusCensored RecordStatusT = 3
   259  
   260  	// RecordStatusArchived indicates a record has been archived. An
   261  	// archived record is locked from any further updates. An archived
   262  	// record have a state of either unvetted or vetted.
   263  	RecordStatusArchived RecordStatusT = 4
   264  
   265  	// RecordStatusLast unit test only.
   266  	RecordStatusLast RecordStatusT = 5
   267  )
   268  
   269  var (
   270  	// RecordStatuses contains the human readable record statuses.
   271  	RecordStatuses = map[RecordStatusT]string{
   272  		RecordStatusInvalid:    "invalid",
   273  		RecordStatusUnreviewed: "unreviewed",
   274  		RecordStatusPublic:     "public",
   275  		RecordStatusCensored:   "censored",
   276  		RecordStatusArchived:   "archived",
   277  	}
   278  )
   279  
   280  // File describes an individual file that is part of the record.
   281  type File struct {
   282  	Name    string `json:"name"`    // Filename
   283  	MIME    string `json:"mime"`    // Mime type
   284  	Digest  string `json:"digest"`  // SHA256 digest of unencoded payload
   285  	Payload string `json:"payload"` // File content, base64 encoded
   286  }
   287  
   288  // MetadataStream describes a record metadata stream.
   289  type MetadataStream struct {
   290  	PluginID string `json:"pluginid"`
   291  	StreamID uint32 `json:"streamid"`
   292  	Payload  string `json:"payload"` // JSON encoded
   293  }
   294  
   295  // CensorshipRecord contains cryptographic proof that a record was accepted for
   296  // review by the server. The proof is verifiable by the client.
   297  type CensorshipRecord struct {
   298  	// Token is a random censorship token that is generated by the
   299  	// server. It serves as a unique identifier for the record.
   300  	Token string `json:"token"`
   301  
   302  	// Merkle is the ordered merkle root of all files in the record.
   303  	Merkle string `json:"merkle"`
   304  
   305  	// Signature is the server signature of the Merkle+Token.
   306  	Signature string `json:"signature"`
   307  }
   308  
   309  // Record represents a record and all of its content.
   310  type Record struct {
   311  	State     RecordStateT     `json:"state"`     // Record state
   312  	Status    RecordStatusT    `json:"status"`    // Record status
   313  	Version   uint32           `json:"version"`   // Version of this record
   314  	Timestamp int64            `json:"timestamp"` // Last update
   315  	Username  string           `json:"username"`  // Author username
   316  	Metadata  []MetadataStream `json:"metadata"`  // Metadata streams
   317  	Files     []File           `json:"files"`     // User submitted files
   318  
   319  	CensorshipRecord CensorshipRecord `json:"censorshiprecord"`
   320  }
   321  
   322  // UserMetadata contains user metadata about a politeiad record. It is
   323  // generated by the server and saved to politeiad as a metadata stream.
   324  //
   325  // Signature is the client signature of the record merkle root. The merkle root
   326  // is the ordered merkle root of all user submitted politeiad files.
   327  type UserMetadata struct {
   328  	UserID    string `json:"userid"`    // Author user ID
   329  	PublicKey string `json:"publickey"` // Key used for signature
   330  	Signature string `json:"signature"` // Signature of merkle root
   331  }
   332  
   333  // StatusChange represents a record status change. It is generated by the
   334  // server and saved to politeiad as a metadata stream.
   335  //
   336  // Signature is the client signature of the Token+Version+Status+Reason.
   337  type StatusChange struct {
   338  	Token     string        `json:"token"`
   339  	Version   uint32        `json:"version"`
   340  	Status    RecordStatusT `json:"status"`
   341  	Reason    string        `json:"reason,omitempty"`
   342  	PublicKey string        `json:"publickey"`
   343  	Signature string        `json:"signature"`
   344  	Timestamp int64         `json:"timestamp"`
   345  }
   346  
   347  // New submits a new record.
   348  //
   349  // Signature is the client signature of the record merkle root. The merkle root
   350  // is the ordered merkle root of all record Files.
   351  type New struct {
   352  	Files     []File `json:"files"`
   353  	PublicKey string `json:"publickey"`
   354  	Signature string `json:"signature"`
   355  }
   356  
   357  // NewReply is the reply to the New command.
   358  type NewReply struct {
   359  	Record Record `json:"record"`
   360  }
   361  
   362  // Edit edits an existing record.
   363  //
   364  // Signature is the client signature of the record merkle root. The merkle root
   365  // is the ordered merkle root of all record Files.
   366  type Edit struct {
   367  	Token     string `json:"token"`
   368  	Files     []File `json:"files"`
   369  	PublicKey string `json:"publickey"`
   370  	Signature string `json:"signature"`
   371  }
   372  
   373  // EditReply is the reply to the Edit command.
   374  type EditReply struct {
   375  	Record Record `json:"record"`
   376  }
   377  
   378  // SetStatus sets the status of a record. Some status changes require a reason
   379  // to be included.
   380  //
   381  // Signature is the client signature of the Token+Version+Status+Reason.
   382  type SetStatus struct {
   383  	Token     string        `json:"token"`
   384  	Version   uint32        `json:"version"`
   385  	Status    RecordStatusT `json:"status"`
   386  	Reason    string        `json:"reason,omitempty"`
   387  	PublicKey string        `json:"publickey"`
   388  	Signature string        `json:"signature"`
   389  }
   390  
   391  // SetStatusReply is the reply to the SetStatus command.
   392  type SetStatusReply struct {
   393  	Record Record `json:"record"`
   394  }
   395  
   396  // Details requests the details of a record. The full record will be returned.
   397  // If no version is specified then the most recent version will be returned.
   398  type Details struct {
   399  	Token   string `json:"token"`
   400  	Version uint32 `json:"version,omitempty"`
   401  }
   402  
   403  // DetailsReply is the reply to the Details command.
   404  type DetailsReply struct {
   405  	Record Record `json:"record"`
   406  }
   407  
   408  // Proof contains an inclusion proof for the digest in the merkle root. All
   409  // digests are hex encoded SHA256 digests.
   410  //
   411  // The ExtraData field is used by certain types of proofs to include
   412  // additional data that is required to validate the proof.
   413  type Proof struct {
   414  	Type       string   `json:"type"`
   415  	Digest     string   `json:"digest"`
   416  	MerkleRoot string   `json:"merkleroot"`
   417  	MerklePath []string `json:"merklepath"`
   418  	ExtraData  string   `json:"extradata"` // JSON encoded
   419  }
   420  
   421  // Timestamp contains all of the data required to verify that a piece of
   422  // record data was timestamped onto the decred blockchain.
   423  //
   424  // All digests are hex encoded SHA256 digests. The merkle root can be found
   425  // in the OP_RETURN of the specified DCR transaction.
   426  //
   427  // TxID, MerkleRoot, and Proofs will only be populated once the merkle root
   428  // has been included in a DCR tx and the tx has 6 confirmations. The Data
   429  // field will not be populated if the data has been censored.
   430  type Timestamp struct {
   431  	Data       string  `json:"data"` // JSON encoded
   432  	Digest     string  `json:"digest"`
   433  	TxID       string  `json:"txid"`
   434  	MerkleRoot string  `json:"merkleroot"`
   435  	Proofs     []Proof `json:"proofs"`
   436  }
   437  
   438  // Timestamps requests the timestamps for a specific record version. If the
   439  // version is omitted, the timestamps for the most recent version will be
   440  // returned.
   441  type Timestamps struct {
   442  	Token   string `json:"token"`
   443  	Version uint32 `json:"version,omitempty"`
   444  }
   445  
   446  // TimestampsReply is the reply to the Timestamps command.
   447  type TimestampsReply struct {
   448  	RecordMetadata Timestamp `json:"recordmetadata"`
   449  
   450  	// map[pluginID]map[streamID]Timestamp
   451  	Metadata map[string]map[uint32]Timestamp `json:"metadata"`
   452  
   453  	// map[filename]Timestamp
   454  	Files map[string]Timestamp `json:"files"`
   455  }
   456  
   457  const (
   458  	// RecordsPageSize is the maximum number of records that can be
   459  	// requested in a Records request.
   460  	RecordsPageSize = 5
   461  )
   462  
   463  // RecordRequest is used to requests select content from a record. The latest
   464  // version of the record is returned. By default, all record files will be
   465  // stripped from the record before being returned.
   466  //
   467  // Filenames can be used to request specific files. If filenames is empty than
   468  // no record files will be returned.
   469  type RecordRequest struct {
   470  	Token     string   `json:"token"`
   471  	Filenames []string `json:"filenames,omitempty"`
   472  }
   473  
   474  // Records requests a batch of records. This route should be used when the
   475  // client only requires select content from the record. The Details command
   476  // should be used when the full record content is required. Unvetted record
   477  // files are only returned to admins and the author.
   478  type Records struct {
   479  	Requests []RecordRequest `json:"requests"`
   480  }
   481  
   482  // RecordsReply is the reply to the Records command. Any tokens that did not
   483  // correspond to a record will not be included in the reply.
   484  //
   485  // **Note** partial record's merkle root is not verifiable - when generating
   486  // the record's merkle all files must be present.
   487  type RecordsReply struct {
   488  	Records map[string]Record `json:"records"` // [token]Record
   489  }
   490  
   491  const (
   492  	// InventoryPageSize is the number of tokens that will be returned
   493  	// per page for all inventory commands.
   494  	InventoryPageSize uint32 = 20
   495  )
   496  
   497  // Inventory requests the tokens of the records in the inventory, categorized
   498  // by record state and record status. The tokens are ordered by the timestamp
   499  // of their most recent status change, sorted from newest to oldest.
   500  //
   501  // The state, status, and page arguments can be provided to request a specific
   502  // page of record tokens.
   503  //
   504  // If no status is provided then a page of tokens for all statuses are
   505  // returned. The state and page arguments will be ignored.
   506  //
   507  // Unvetted record tokens will only be returned to admins.
   508  type Inventory struct {
   509  	State  RecordStateT  `json:"state,omitempty"`
   510  	Status RecordStatusT `json:"status,omitempty"`
   511  	Page   uint32        `json:"page,omitempty"`
   512  }
   513  
   514  // InventoryReply is the reply to the Inventory command. The returned maps are
   515  // map[status][]token where the status is the human readable record status
   516  // defined by the RecordStatuses array in this package.
   517  type InventoryReply struct {
   518  	Unvetted map[string][]string `json:"unvetted"`
   519  	Vetted   map[string][]string `json:"vetted"`
   520  }
   521  
   522  // InventoryOrdered requests a page of record tokens ordered by the timestamp
   523  // of their most recent status change from newest to oldest. The reply will
   524  // include tokens for all record statuses. Unvetted tokens will only be
   525  // returned to admins.
   526  type InventoryOrdered struct {
   527  	State RecordStateT `json:"state"`
   528  	Page  uint32       `json:"page"`
   529  }
   530  
   531  // InventoryOrderedReply is the reply to the InventoryOrdered command.
   532  type InventoryOrderedReply struct {
   533  	Tokens []string `json:"tokens"`
   534  }
   535  
   536  // UserRecords requests the tokens of all records submitted by a user.
   537  // Unvetted record tokens are only returned to admins and the record author.
   538  type UserRecords struct {
   539  	UserID string `json:"userid"`
   540  }
   541  
   542  // UserRecordsReply is the reply to the UserRecords command.
   543  type UserRecordsReply struct {
   544  	Unvetted []string `json:"unvetted"`
   545  	Vetted   []string `json:"vetted"`
   546  }