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  }