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 }