github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/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 6 7 import ( 8 "os" 9 "path/filepath" 10 "strconv" 11 "sync" 12 13 "github.com/decred/politeia/politeiad/api/v1/identity" 14 backend "github.com/decred/politeia/politeiad/backendv2" 15 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins" 16 "github.com/decred/politeia/politeiad/plugins/comments" 17 "github.com/pkg/errors" 18 ) 19 20 var ( 21 _ plugins.PluginClient = (*commentsPlugin)(nil) 22 ) 23 24 // commentsPlugin is the tstore backend implementation of the comments plugin. 25 // The comments plugin extends a record with comment functionality. 26 // 27 // commentsPlugin satisfies the plugins PluginClient interface. 28 type commentsPlugin struct { 29 sync.RWMutex 30 tstore plugins.TstoreClient 31 32 // dataDir is the comments plugin data directory. The only data 33 // that is stored here is cached data that can be re-created at any 34 // time by walking the trillian trees. 35 dataDir string 36 37 // identity contains the full identity that the plugin uses to 38 // create receipts, i.e. signatures of user provided data that 39 // prove the backend received and processed a plugin command. 40 identity *identity.FullIdentity 41 42 // Plugin settings 43 commentLengthMax uint32 44 voteChangesMax uint32 45 allowExtraData bool 46 votesPageSize uint32 47 countPageSize uint32 48 timestampsPageSize uint32 49 allowEdits bool 50 editPeriod uint32 51 } 52 53 // Setup performs any plugin setup that is required. 54 // 55 // This function satisfies the plugins PluginClient interface. 56 func (p *commentsPlugin) Setup() error { 57 log.Tracef("comments Setup") 58 59 return nil 60 } 61 62 // Cmd executes a plugin command. 63 // 64 // This function satisfies the plugins PluginClient interface. 65 func (p *commentsPlugin) Cmd(token []byte, cmd, payload string) (string, error) { 66 log.Tracef("comments Cmd: %x %v %v", token, cmd, payload) 67 68 switch cmd { 69 case comments.CmdNew: 70 return p.cmdNew(token, payload) 71 case comments.CmdEdit: 72 return p.cmdEdit(token, payload) 73 case comments.CmdDel: 74 return p.cmdDel(token, payload) 75 case comments.CmdVote: 76 return p.cmdVote(token, payload) 77 case comments.CmdGet: 78 return p.cmdGet(token, payload) 79 case comments.CmdGetAll: 80 return p.cmdGetAll(token) 81 case comments.CmdGetVersion: 82 return p.cmdGetVersion(token, payload) 83 case comments.CmdCount: 84 return p.cmdCount(token) 85 case comments.CmdVotes: 86 return p.cmdVotes(token, payload) 87 case comments.CmdTimestamps: 88 return p.cmdTimestamps(token, payload) 89 } 90 91 return "", backend.ErrPluginCmdInvalid 92 } 93 94 // Hook executes a plugin hook. 95 // 96 // This function satisfies the plugins PluginClient interface. 97 func (p *commentsPlugin) Hook(h plugins.HookT, payload string) error { 98 log.Tracef("comments Hook: %x %v", plugins.Hooks[h]) 99 100 return nil 101 } 102 103 // Fsck performs a plugin file system check. The plugin is provided with the 104 // tokens for all records in the backend. 105 // 106 // This function satisfies the plugins PluginClient interface. 107 func (p *commentsPlugin) Fsck(tokens [][]byte) error { 108 log.Infof("Comments fsck starting for %v records", len(tokens)) 109 110 // Range the provided record tokens and verify that the 111 // cached record index is coherent for each token. The 112 // cache entry will be built from scratch if any errors 113 // are found with it. 114 var rebuilt int 115 for i, token := range tokens { 116 log.Debugf("Comments fsck for record %v/%v", i+1, len(tokens)) 117 118 wasRebuilt, err := p.fsckRecordIndex(token) 119 if err != nil { 120 return err 121 } 122 if wasRebuilt { 123 rebuilt++ 124 } 125 } 126 127 log.Infof("%v/%v record indexes required a rebuild", rebuilt, len(tokens)) 128 log.Infof("Comments fsck complete") 129 130 return nil 131 } 132 133 // Settings returns the plugin settings. 134 // 135 // This function satisfies the plugins PluginClient interface. 136 func (p *commentsPlugin) Settings() []backend.PluginSetting { 137 log.Tracef("comments Settings") 138 139 return []backend.PluginSetting{ 140 { 141 Key: comments.SettingKeyCommentLengthMax, 142 Value: strconv.FormatUint(uint64(p.commentLengthMax), 10), 143 }, 144 { 145 Key: comments.SettingKeyVoteChangesMax, 146 Value: strconv.FormatUint(uint64(p.voteChangesMax), 10), 147 }, 148 { 149 Key: comments.SettingKeyAllowExtraData, 150 Value: strconv.FormatBool(p.allowExtraData), 151 }, 152 { 153 Key: comments.SettingKeyVotesPageSize, 154 Value: strconv.FormatUint(uint64(p.votesPageSize), 10), 155 }, 156 { 157 Key: comments.SettingKeyCountPageSize, 158 Value: strconv.FormatUint(uint64(p.countPageSize), 10), 159 }, 160 { 161 Key: comments.SettingKeyTimestampsPageSize, 162 Value: strconv.FormatUint(uint64(p.timestampsPageSize), 10), 163 }, 164 { 165 Key: comments.SettingKeyAllowEdits, 166 Value: strconv.FormatBool(p.allowEdits), 167 }, 168 { 169 Key: comments.SettingKeyEditPeriod, 170 Value: strconv.FormatUint(uint64(p.editPeriod), 10), 171 }, 172 } 173 } 174 175 // New returns a new comments plugin. 176 func New(tstore plugins.TstoreClient, settings []backend.PluginSetting, dataDir string, id *identity.FullIdentity) (*commentsPlugin, error) { 177 // Setup comments plugin data dir 178 dataDir = filepath.Join(dataDir, comments.PluginID) 179 err := os.MkdirAll(dataDir, 0700) 180 if err != nil { 181 return nil, err 182 } 183 184 // Default plugin settings 185 var ( 186 commentLengthMax = comments.SettingCommentLengthMax 187 voteChangesMax = comments.SettingVoteChangesMax 188 allowExtraData = comments.SettingAllowExtraData 189 votesPageSize = comments.SettingVotesPageSize 190 countPageSize = comments.SettingCountPageSize 191 timestampsPageSize = comments.SettingTimestampsPageSize 192 allowEdits = comments.SettingAllowEdits 193 editPeriod = comments.SettingEditPeriod 194 ) 195 196 // Override defaults with any passed in settings 197 for _, v := range settings { 198 switch v.Key { 199 case comments.SettingKeyCommentLengthMax: 200 u, err := strconv.ParseUint(v.Value, 10, 64) 201 if err != nil { 202 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 203 v.Key, v.Value, err) 204 } 205 commentLengthMax = uint32(u) 206 207 case comments.SettingKeyVoteChangesMax: 208 u, err := strconv.ParseUint(v.Value, 10, 64) 209 if err != nil { 210 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 211 v.Key, v.Value, err) 212 } 213 voteChangesMax = uint32(u) 214 215 case comments.SettingKeyAllowExtraData: 216 b, err := strconv.ParseBool(v.Value) 217 if err != nil { 218 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 219 v.Key, v.Value, err) 220 } 221 allowExtraData = b 222 223 case comments.SettingKeyVotesPageSize: 224 u, err := strconv.ParseUint(v.Value, 10, 64) 225 if err != nil { 226 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 227 v.Key, v.Value, err) 228 } 229 votesPageSize = uint32(u) 230 231 case comments.SettingKeyCountPageSize: 232 u, err := strconv.ParseUint(v.Value, 10, 64) 233 if err != nil { 234 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 235 v.Key, v.Value, err) 236 } 237 countPageSize = uint32(u) 238 239 case comments.SettingKeyTimestampsPageSize: 240 u, err := strconv.ParseUint(v.Value, 10, 64) 241 if err != nil { 242 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 243 v.Key, v.Value, err) 244 } 245 timestampsPageSize = uint32(u) 246 247 case comments.SettingKeyAllowEdits: 248 b, err := strconv.ParseBool(v.Value) 249 if err != nil { 250 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 251 v.Key, v.Value, err) 252 } 253 allowEdits = b 254 255 case comments.SettingKeyEditPeriod: 256 u, err := strconv.ParseUint(v.Value, 10, 64) 257 if err != nil { 258 return nil, errors.Errorf("invalid plugin setting %v '%v': %v", 259 v.Key, v.Value, err) 260 } 261 editPeriod = uint32(u) 262 263 default: 264 return nil, errors.Errorf("invalid comments plugin setting '%v'", v.Key) 265 } 266 } 267 268 return &commentsPlugin{ 269 tstore: tstore, 270 identity: id, 271 dataDir: dataDir, 272 commentLengthMax: commentLengthMax, 273 voteChangesMax: voteChangesMax, 274 allowExtraData: allowExtraData, 275 votesPageSize: votesPageSize, 276 countPageSize: countPageSize, 277 timestampsPageSize: timestampsPageSize, 278 allowEdits: allowEdits, 279 editPeriod: editPeriod, 280 }, nil 281 }