github.com/decred/politeia@v1.4.0/politeiad/plugins/comments/comments.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 comments provides a plugin for extending a record with comment 6 // functionality. 7 package comments 8 9 const ( 10 // PluginID is the unique identifier for this plugin. 11 PluginID = "comments" 12 13 // Plugin commands 14 CmdNew = "new" // Create a new comment 15 CmdEdit = "edit" // Edit a comment 16 CmdDel = "del" // Del a comment 17 CmdVote = "vote" // Vote on a comment 18 CmdGet = "get" // Get specified comments 19 CmdGetAll = "getall" // Get all comments for a record 20 CmdGetVersion = "getversion" // Get specified version of a comment 21 CmdCount = "count" // Get comments count for a record 22 CmdVotes = "votes" // Get comment votes 23 CmdTimestamps = "timestamps" // Get timestamps 24 ) 25 26 // Plugin setting keys can be used to specify custom plugin settings. Default 27 // plugin setting values can be overridden by providing a plugin setting key 28 // and value to the plugin on startup. 29 const ( 30 // SettingKeyCommentLengthMax is the plugin setting key for the 31 // SettingCommentLengthMax plugin setting. 32 SettingKeyCommentLengthMax = "commentlengthmax" 33 34 // SettingKeyVoteChangesMax is the plugin setting key for the 35 // SettingVoteChangesMax plugin setting. 36 SettingKeyVoteChangesMax = "votechangesmax" 37 38 // SettingKeyAllowExtraData is the plugin setting key for the 39 // SettingAllowExtraData plugin setting. 40 SettingKeyAllowExtraData = "allowextradata" 41 42 // SettingKeyVotesPageSize is the plugin setting key for the 43 // SettingVotesPageSize plugin setting. 44 SettingKeyVotesPageSize = "votespagesize" 45 46 // SettingKeyCountPageSize is the plugin setting key for the 47 // SettingCountPageSize plugin setting. 48 SettingKeyCountPageSize = "countpagesize" 49 50 // SettingKeyTimestampsPageSize is the plugin setting key for the 51 // SettingTimestampsPageSize plugin setting. 52 SettingKeyTimestampsPageSize = "timestampspagesize" 53 54 // SettingKeyAllowEdits is the plugin setting key for the 55 // SettingAllowEdits plugin setting. 56 SettingKeyAllowEdits = "allowedits" 57 58 // SettingKeyEditPeriod is the plugin setting key for the 59 // SettingEditPeriod plugin setting. 60 SettingKeyEditPeriod = "editperiod" 61 ) 62 63 // Plugin setting default values. These can be overridden by providing a 64 // plugin setting key and value to the plugin on startup. 65 const ( 66 // SettingCommentLengthMax is the default maximum number of 67 // characters that are allowed in a comment. 68 SettingCommentLengthMax uint32 = 8000 69 70 // SettingVoteChangesMax is the default maximum number of times a 71 // user can change their vote on a comment. This prevents a 72 // malicious user from being able to spam comment votes. 73 SettingVoteChangesMax uint32 = 5 74 75 // SettingAllowExtraData is the default value of the bool flag which 76 // determines whether posting extra data along with the comment is allowed. 77 SettingAllowExtraData = false 78 79 // SettingVotesPageSize is the default maximum number of comment votes 80 // that can be returned at any one time. It defaults to 2500 to limit the 81 // comment votes route payload size to be ~1MiB, as each comment vote size 82 // is expected to be around 400 bytes which means: 83 // 2500 * 400 byte = 1000000 byte = ~1MiB. 84 SettingVotesPageSize uint32 = 2500 85 86 // SettingCountPageSize is the default maximum number of comment counts 87 // that can be requested at any one time. 88 SettingCountPageSize uint32 = 10 89 90 // SettingTimestampsPageSize is the default maximum number of comment 91 // timestamps that can be requested at any one time. 92 SettingTimestampsPageSize uint32 = 100 93 94 // SettingAllowEdits is the default value of the bool flag which 95 // determines whether comment edits are temporarily allowed during the 96 // timeframe set by SettingEditPeriod. 97 SettingAllowEdits = false 98 99 // SettingEditPeriod is the default maximum amount of time, 100 // in seconds, since the submission of a comment where it's still 101 // editable. It defaults to five minutes which should be enough time 102 // to spot typos and grammar mistakes. 103 SettingEditPeriod uint32 = 300 104 ) 105 106 // ErrorCodeT represents a error that was caused by the user. 107 type ErrorCodeT uint32 108 109 const ( 110 // ErrorCodeInvalid is an invalid error code. 111 ErrorCodeInvalid ErrorCodeT = 0 112 113 // ErrorCodeTokenInvalid is returned when a token is invalid. 114 ErrorCodeTokenInvalid ErrorCodeT = 1 115 116 // ErrorCodePublicKeyInvalid is returned when a public key is 117 // invalid. 118 ErrorCodePublicKeyInvalid ErrorCodeT = 2 119 120 // ErrorCodeSignatureInvalid is returned when a signature is 121 // invalid. 122 ErrorCodeSignatureInvalid ErrorCodeT = 3 123 124 // ErrorCodeMaxLengthExceeded is returned when a comment exceeds the 125 // max length plugin setting. 126 ErrorCodeMaxLengthExceeded ErrorCodeT = 4 127 128 // ErrorCodeNoChanges is returned when a comment edit does not 129 // contain any changes. 130 ErrorCodeNoChanges ErrorCodeT = 5 131 132 // ErrorCodeCommentNotFound is returned when a comment could not be 133 // found. 134 ErrorCodeCommentNotFound ErrorCodeT = 6 135 136 // ErrorCodeUserUnauthorized is returned when a user is attempting 137 // to edit a comment that they did not submit. 138 ErrorCodeUserUnauthorized ErrorCodeT = 7 139 140 // ErrorCodeParentIDInvalid is returned when a comment parent ID 141 // does not correspond to an actual comment. 142 ErrorCodeParentIDInvalid ErrorCodeT = 8 143 144 // ErrorCodeVoteInvalid is returned when a comment vote is invalid. 145 ErrorCodeVoteInvalid ErrorCodeT = 9 146 147 // ErrorCodeVoteChangesMaxExceeded is returned when the number of 148 // times the user has changed their vote has exceeded the vote 149 // changes max plugin setting. 150 ErrorCodeVoteChangesMaxExceeded ErrorCodeT = 10 151 152 // ErrorCodeRecordStateInvalid is returned when the provided state 153 // does not match the record state. 154 ErrorCodeRecordStateInvalid ErrorCodeT = 11 155 156 // ErrorCodeExtraDataNotAllowed is returned when comment extra data 157 // is found while comment plugin setting does not allow it. 158 ErrorCodeExtraDataNotAllowed = 12 159 160 // ErrorCodeEditNotAllowed is returned when comment edit is not 161 // allowed. 162 ErrorCodeEditNotAllowed = 13 163 164 // ErrorCodeEmptyComment is returned when a comment with no comment text 165 // is submitted. 166 ErrorCodeEmptyComment = 14 167 168 // ErrorCodeLast is used by unit tests to verify that all error codes have 169 // a human readable entry in the ErrorCodes map. This error code will never 170 // be returned. 171 ErrorCodeLast ErrorCodeT = 15 172 ) 173 174 var ( 175 // ErrorCodes contains the human readable error messages. 176 ErrorCodes = map[ErrorCodeT]string{ 177 ErrorCodeInvalid: "error code invalid", 178 ErrorCodeTokenInvalid: "token invalid", 179 ErrorCodePublicKeyInvalid: "public key invalid", 180 ErrorCodeSignatureInvalid: "signature invalid", 181 ErrorCodeMaxLengthExceeded: "max length exceeded", 182 ErrorCodeNoChanges: "no changes", 183 ErrorCodeCommentNotFound: "comment not found", 184 ErrorCodeUserUnauthorized: "user unauthorized", 185 ErrorCodeParentIDInvalid: "parent id invalid", 186 ErrorCodeVoteInvalid: "vote invalid", 187 ErrorCodeVoteChangesMaxExceeded: "vote changes max exceeded", 188 ErrorCodeRecordStateInvalid: "record state invalid", 189 ErrorCodeExtraDataNotAllowed: "comment extra data not allowed", 190 ErrorCodeEditNotAllowed: "comment edit is not allowed", 191 ErrorCodeEmptyComment: "comment is empty", 192 } 193 ) 194 195 // RecordStateT represents the state of a record. 196 type RecordStateT uint32 197 198 const ( 199 // RecordStateInvalid is an invalid record state. 200 RecordStateInvalid RecordStateT = 0 201 202 // RecordStateUnvetted indicates a record has not been made public. 203 RecordStateUnvetted RecordStateT = 1 204 205 // RecordStateVetted indicates a record has been made public. 206 RecordStateVetted RecordStateT = 2 207 ) 208 209 // Comment represent a record comment. 210 // 211 // A parent ID of 0 indicates that the comment is a base level comment and not 212 // a reply commment. 213 // 214 // Comments made on a record when it is unvetted and when it is vetted are 215 // treated as two distinct groups of comments. When a record becomes vetted the 216 // comment ID starts back at 1. 217 // 218 // If a comment is deleted the PublicKey, Signature, Receipt, and Timestamp 219 // fields will be the from the deletion action, not from the original comment. 220 // The only field that is retained from the original comment is the UserID 221 // field so that the client can still display the correct user information for 222 // the deleted comment. Everything else from the original comment is 223 // permanently deleted. 224 // 225 // PublicKey is the user's public key that is used to verify the signature. 226 // 227 // Signature is the user signature of the: 228 // State + Token + ParentID + Comment + ExtraData + ExtraDataHint 229 // 230 // Receipt is the server signature of the user signature. 231 // 232 // The PublicKey, Signature, and Receipt are all hex encoded and use the 233 // ed25519 signature scheme. 234 type Comment struct { 235 UserID string `json:"userid"` // Unique user ID 236 State RecordStateT `json:"state"` // Record state 237 Token string `json:"token"` // Record token 238 ParentID uint32 `json:"parentid"` // Parent comment ID if reply 239 Comment string `json:"comment"` // Comment text 240 PublicKey string `json:"publickey"` // Public key used for Signature 241 Signature string `json:"signature"` // Client signature 242 CommentID uint32 `json:"commentid"` // Comment ID 243 Version uint32 `json:"version"` // Comment version 244 CreatedAt int64 `json:"createdat"` // UNIX timestamp of creation time 245 Timestamp int64 `json:"timestamp"` // UNIX timestamp of last edit 246 Receipt string `json:"receipt"` // Server sig of client sig 247 Downvotes uint64 `json:"downvotes"` // Tolal downvotes on comment 248 Upvotes uint64 `json:"upvotes"` // Total upvotes on comment 249 250 Deleted bool `json:"deleted,omitempty"` // Comment has been deleted 251 Reason string `json:"reason,omitempty"` // Reason for deletion 252 253 // Optional fields to be used freely 254 ExtraData string `json:"extradata,omitempty"` 255 ExtraDataHint string `json:"extradatahint,omitempty"` 256 } 257 258 // CommentAdd is the structure that is saved to disk when a comment is created 259 // or edited. 260 // 261 // PublicKey is the user's public key that is used to verify the signature. 262 // 263 // The structure of the signature field depends on whether the CommentAdd is 264 // associated with a new comment or a comment edit: 265 // 266 // 1. When a comment is created it's the user signature of the: 267 // State + Token + ParentID + Comment + ExtraData + ExtraDataHint. 268 // 269 // 2. When a comment is edited it's the user signature of the: 270 // State + Token + ParentID + CommentID + Comment + ExtraData + ExtraDataHint. 271 // 272 // Receipt is the server signature of the user signature. 273 // 274 // The PublicKey, Signature, and Receipt are all hex encoded and use the 275 // ed25519 signature scheme. 276 type CommentAdd struct { 277 // Data generated by client 278 UserID string `json:"userid"` // Unique user ID 279 State RecordStateT `json:"state"` // Record state 280 Token string `json:"token"` // Record token 281 ParentID uint32 `json:"parentid"` // Parent comment ID 282 Comment string `json:"comment"` // Comment 283 PublicKey string `json:"publickey"` // Pubkey used for Signature 284 Signature string `json:"signature"` // Client signature 285 286 // Metadata generated by server 287 CommentID uint32 `json:"commentid"` // Comment ID 288 Version uint32 `json:"version"` // Comment version 289 Timestamp int64 `json:"timestamp"` // Received UNIX timestamp 290 Receipt string `json:"receipt"` // Server signature of client signature 291 292 // Optional fields to be used freely 293 ExtraData string `json:"extradata,omitempty"` 294 ExtraDataHint string `json:"extradatahint,omitempty"` 295 } 296 297 // CommentDel is the structure that is saved to disk when a comment is deleted. 298 // Some additional fields like ParentID and UserID are required to be saved 299 // since all the CommentAdd records will be deleted and the client needs these 300 // additional fields to properly display the deleted comment in the comment 301 // hierarchy. 302 // 303 // PublicKey is the user's public key that is used to verify the signature. 304 // 305 // Signature is the user signature of the: 306 // State + Token + CommentID + Reason 307 // 308 // The PublicKey and Signature are hex encoded and use the 309 // ed25519 signature scheme. 310 type CommentDel struct { 311 // Data generated by client 312 Token string `json:"token"` // Record token 313 State RecordStateT `json:"state"` // Record state 314 CommentID uint32 `json:"commentid"` // Comment ID 315 Reason string `json:"reason"` // Reason for deleting 316 PublicKey string `json:"publickey"` // Pubkey used for Signature 317 Signature string `json:"signature"` // Client signature 318 319 // Metadata generated by server 320 ParentID uint32 `json:"parentid"` // Parent comment ID 321 UserID string `json:"userid"` // Author user ID 322 Timestamp int64 `json:"timestamp"` // Received UNIX timestamp 323 Receipt string `json:"receipt"` // Server sig of client sig 324 } 325 326 // VoteT represents a comment upvote/downvote. 327 type VoteT int32 328 329 const ( 330 // VoteInvalid is an invalid comment vote. 331 VoteInvalid VoteT = 0 332 333 // VoteDownvote represents a comment downvote. 334 VoteDownvote VoteT = -1 335 336 // VoteUpvote represents a comment upvote. 337 VoteUpvote VoteT = 1 338 ) 339 340 // CommentVote is the structure that is saved to disk when a comment is voted 341 // on. 342 // 343 // PublicKey is the user's public key that is used to verify the signature. 344 // 345 // Signature is the user signature of the: 346 // State + Token + CommentID + Vote 347 // 348 // The PublicKey and Signature are hex encoded and use the 349 // ed25519 signature scheme. 350 type CommentVote struct { 351 // Data generated by client 352 UserID string `json:"userid"` // Unique user ID 353 State RecordStateT `json:"state"` // Record state 354 Token string `json:"token"` // Record token 355 CommentID uint32 `json:"commentid"` // Comment ID 356 Vote VoteT `json:"vote"` // Upvote or downvote 357 PublicKey string `json:"publickey"` // Public key used for signature 358 Signature string `json:"signature"` // Client signature 359 360 // Metadata generated by server 361 Timestamp int64 `json:"timestamp"` // Received UNIX timestamp 362 Receipt string `json:"receipt"` // Server signature of client signature 363 } 364 365 // New creates a new comment. 366 // 367 // The parent ID is used to reply to an existing comment. A parent ID of 0 368 // indicates that the comment is a base level comment and not a reply commment. 369 // 370 // PublicKey is the user's public key that is used to verify the signature. 371 // 372 // Signature is the user signature of the: 373 // State + Token + ParentID + Comment + ExtraData + ExtraDataHint 374 // 375 // Receipt is the server signature of the user signature. 376 // 377 // The PublicKey, Signature, and Receipt are all hex encoded and use the 378 // ed25519 signature scheme. 379 type New struct { 380 UserID string `json:"userid"` // Unique user ID 381 State RecordStateT `json:"state"` // Record state 382 Token string `json:"token"` // Record token 383 ParentID uint32 `json:"parentid"` // Parent comment ID 384 Comment string `json:"comment"` // Comment text 385 PublicKey string `json:"publickey"` // Pubkey used for Signature 386 Signature string `json:"signature"` // Client signature 387 388 // Optional fields to be used freely 389 ExtraData string `json:"extradata,omitempty"` 390 ExtraDataHint string `json:"extradatahint,omitempty"` 391 } 392 393 // NewReply is the reply to the New command. 394 type NewReply struct { 395 Comment Comment `json:"comment"` 396 } 397 398 // Edit edits an existing comment. 399 // 400 // PublicKey is the user's public key that is used to verify the signature. 401 // 402 // Signature is the user signature of the: 403 // State + Token + ParentID + CommentID + Comment + ExtraData + ExtraDataHint 404 // 405 // Receipt is the server signature of the user signature. 406 // 407 // The PublicKey, Signature, and Receipt are all hex encoded and use the 408 // ed25519 signature scheme. 409 type Edit struct { 410 UserID string `json:"userid"` // Unique user ID 411 State RecordStateT `json:"state"` // Record state 412 Token string `json:"token"` // Record token 413 ParentID uint32 `json:"parentid"` // Parent comment ID 414 CommentID uint32 `json:"commentid"` // Comment ID 415 Comment string `json:"comment"` // Comment text 416 PublicKey string `json:"publickey"` // Pubkey used for Signature 417 Signature string `json:"signature"` // Client signature 418 419 // Optional fields to be used freely 420 ExtraData string `json:"extradata,omitempty"` 421 ExtraDataHint string `json:"extradatahint,omitempty"` 422 } 423 424 // EditReply is the reply to the Edit command. 425 type EditReply struct { 426 Comment Comment `json:"comment"` 427 } 428 429 // Del permanently deletes all versions of the provided comment. 430 // 431 // PublicKey is the user's public key that is used to verify the signature. 432 // 433 // Signature is the user signature of the: 434 // State + Token + CommentID + Reason 435 // 436 // The PublicKey and Signature are hex encoded and use the 437 // ed25519 signature scheme. 438 type Del struct { 439 State RecordStateT `json:"state"` // Record state 440 Token string `json:"token"` // Record token 441 CommentID uint32 `json:"commentid"` // Comment ID 442 Reason string `json:"reason"` // Reason for deletion 443 PublicKey string `json:"publickey"` // Public key used for signature 444 Signature string `json:"signature"` // Client signature 445 } 446 447 // DelReply is the reply to the Del command. 448 type DelReply struct { 449 Comment Comment `json:"comment"` 450 } 451 452 // Vote casts a comment vote (upvote or downvote). 453 // 454 // The effect of a new vote on a comment score depends on the previous vote 455 // from that user ID. Example, a user upvotes a comment that they have already 456 // upvoted, the resulting vote score is 0 due to the second upvote removing the 457 // original upvote. The public key cannot be relied on to remain the same for 458 // each user so a user ID must be included. 459 // 460 // PublicKey is the user's public key that is used to verify the signature. 461 // 462 // Signature is the user signature of the: 463 // State + Token + CommentID + Vote 464 // 465 // The PublicKey and Signature are hex encoded and use the 466 // ed25519 signature scheme. 467 type Vote struct { 468 UserID string `json:"userid"` // Unique user ID 469 State RecordStateT `json:"state"` // Record state 470 Token string `json:"token"` // Record token 471 CommentID uint32 `json:"commentid"` // Comment ID 472 Vote VoteT `json:"vote"` // Upvote or downvote 473 PublicKey string `json:"publickey"` // Public key used for signature 474 Signature string `json:"signature"` // Client signature 475 } 476 477 // VoteReply is the reply to the Vote command. 478 type VoteReply struct { 479 Downvotes uint64 `json:"downvotes"` // Tolal downvotes on comment 480 Upvotes uint64 `json:"upvotes"` // Total upvotes on comment 481 Timestamp int64 `json:"timestamp"` // Received UNIX timestamp 482 Receipt string `json:"receipt"` // Server signature of client signature 483 } 484 485 // Get retrieves a batch of specified comments. The most recent version of each 486 // comment is returned. An error is not returned if a comment is not found for 487 // one or more of the comment IDs. Those entries will simply not be included in 488 // the reply. 489 type Get struct { 490 CommentIDs []uint32 `json:"commentids"` 491 } 492 493 // GetReply is the reply to the Get command. The returned map will not include 494 // an entry for any comment IDs that did not correspond to an actual comment. 495 // It is the responsibility of the caller to ensure that a comment was returned 496 // for all of the provided comment IDs. 497 type GetReply struct { 498 Comments map[uint32]Comment `json:"comments"` // [commentID]Comment 499 } 500 501 // GetAll retrieves all comments for a record. The latest version of each 502 // comment is returned. 503 type GetAll struct{} 504 505 // GetAllReply is the reply to the GetAll command. The returned comments array 506 // is ordered by comment ID from smallest to largest. 507 type GetAllReply struct { 508 Comments []Comment `json:"comments"` 509 } 510 511 // GetVersion retrieves the specified version of a comment. 512 type GetVersion struct { 513 CommentID uint32 `json:"commentid"` 514 Version uint32 `json:"version"` 515 } 516 517 // GetVersionReply is the reply to the GetVersion command. 518 type GetVersionReply struct { 519 Comment Comment `json:"comment"` 520 } 521 522 // Count retrieves the comments count for a record. The comments count is the 523 // number of comments that have been made on a record. 524 type Count struct{} 525 526 // CountReply is the reply to the Count command. 527 type CountReply struct { 528 Count uint32 `json:"count"` 529 } 530 531 // Votes retrieves the record's comment votes that meet the provided filtering 532 // criteria. If no filtering criteria is provided then it rerieves all comment 533 // votes. This command is paginated, if no page is provided, then the first 534 // page is returned. If the requested page does not exist an empty page 535 // is returned. 536 type Votes struct { 537 UserID string `json:"userid,omitempty"` 538 Page uint32 `json:"page,omitempty"` 539 } 540 541 // VotesReply is the reply to the Votes command. 542 type VotesReply struct { 543 Votes []CommentVote `json:"votes"` 544 } 545 546 // Proof contains an inclusion proof for the digest in the merkle root. The 547 // ExtraData field is used by certain types of proofs to include additional 548 // data that is required to validate the proof. 549 type Proof struct { 550 Type string `json:"type"` 551 Digest string `json:"digest"` 552 MerkleRoot string `json:"merkleroot"` 553 MerklePath []string `json:"merklepath"` 554 ExtraData string `json:"extradata"` // JSON encoded 555 } 556 557 // Timestamp contains all of the data required to verify that a piece of data 558 // was timestamped onto the decred blockchain. 559 // 560 // All digests are hex encoded SHA256 digests. The merkle root can be found in 561 // the OP_RETURN of the specified DCR transaction. 562 // 563 // TxID, MerkleRoot, and Proofs will only be populated once the merkle root has 564 // been included in a DCR tx and the tx has 6 confirmations. The Data field 565 // will not be populated if the data has been censored. 566 type Timestamp struct { 567 Data string `json:"data"` // JSON encoded 568 Digest string `json:"digest"` 569 TxID string `json:"txid"` 570 MerkleRoot string `json:"merkleroot"` 571 Proofs []Proof `json:"proofs"` 572 } 573 574 // CommentTimestamp contains the timestamps for the full history of a single 575 // comment. 576 // 577 // A CommentAdd is the structure that is saved to disk anytime a comment is 578 // created or edited. This structure is what will be timestamped. The data 579 // payload of a timestamp in the Adds field will contain a JSON encoded 580 // CommentAdd. 581 // 582 // A CommentDel is the structure that is saved to disk anytime a comment is 583 // deleted. This structure is what will be timestamped. The data payload of a 584 // timestamp in the Del field will contain a JSON encoded CommentDel. 585 // 586 // A CommentVote is the structure that is saved to disk anytime a comment is 587 // voted on. This structure is what will be timestamped. The data payload of 588 // a timestamp in the Votes filed will contain a JSON encoded CommentVote. 589 type CommentTimestamp struct { 590 Adds []Timestamp `json:"adds"` 591 Del *Timestamp `json:"del,omitempty"` 592 Votes []Timestamp `json:"votes,omitempty"` 593 } 594 595 // Timestamps retrieves the timestamps for a record's comments. If a requested 596 // comment ID does not exist, it will not be included in the reply. An error is 597 // not returned. 598 // 599 // If IncludeVotes is set to true then the timestamps for the comment votes 600 // will also be returned. 601 type Timestamps struct { 602 CommentIDs []uint32 `json:"commentids"` 603 IncludeVotes bool `json:"includevotes,omitempty"` 604 } 605 606 // TimestampsReply is the reply to the timestamps command. 607 type TimestampsReply struct { 608 Comments map[uint32]CommentTimestamp `json:"comments"` 609 }