github.com/decred/politeia@v1.4.0/politeiawww/legacy/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  	"encoding/json"
     9  	"net/http"
    10  	"strconv"
    11  
    12  	pdv2 "github.com/decred/politeia/politeiad/api/v2"
    13  	pdclient "github.com/decred/politeia/politeiad/client"
    14  	"github.com/decred/politeia/politeiad/plugins/comments"
    15  	v1 "github.com/decred/politeia/politeiawww/api/comments/v1"
    16  	"github.com/decred/politeia/politeiawww/config"
    17  	"github.com/decred/politeia/politeiawww/legacy/events"
    18  	"github.com/decred/politeia/politeiawww/legacy/sessions"
    19  	"github.com/decred/politeia/politeiawww/legacy/user"
    20  	"github.com/decred/politeia/util"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  // Comments is the context for the comments API.
    25  type Comments struct {
    26  	cfg       *config.Config
    27  	politeiad *pdclient.Client
    28  	userdb    user.Database
    29  	sessions  *sessions.Sessions
    30  	events    *events.Manager
    31  	policy    *v1.PolicyReply
    32  }
    33  
    34  // HandlePolicy is the request handler for the comments v1 Policy route.
    35  func (c *Comments) HandlePolicy(w http.ResponseWriter, r *http.Request) {
    36  	log.Tracef("HandlePolicy")
    37  
    38  	util.RespondWithJSON(w, http.StatusOK, c.policy)
    39  }
    40  
    41  // HandleNew is the request handler for the comments v1 New route.
    42  func (c *Comments) HandleNew(w http.ResponseWriter, r *http.Request) {
    43  	log.Tracef("HandleNew")
    44  
    45  	var n v1.New
    46  	decoder := json.NewDecoder(r.Body)
    47  	if err := decoder.Decode(&n); err != nil {
    48  		respondWithError(w, r, "HandleNew: unmarshal",
    49  			v1.UserErrorReply{
    50  				ErrorCode: v1.ErrorCodeInputInvalid,
    51  			})
    52  		return
    53  	}
    54  
    55  	u, err := c.sessions.GetSessionUser(w, r)
    56  	if err != nil {
    57  		respondWithError(w, r,
    58  			"HandleNew: GetSessionUser: %v", err)
    59  		return
    60  	}
    61  
    62  	nr, err := c.processNew(r.Context(), n, *u)
    63  	if err != nil {
    64  		respondWithError(w, r,
    65  			"HandleNew: processNew: %v", err)
    66  		return
    67  	}
    68  
    69  	util.RespondWithJSON(w, http.StatusOK, nr)
    70  }
    71  
    72  // HandleEdit is the request handler for the comments v1 Edit route.
    73  func (c *Comments) HandleEdit(w http.ResponseWriter, r *http.Request) {
    74  	log.Tracef("HandleEdit")
    75  
    76  	var v v1.Edit
    77  	decoder := json.NewDecoder(r.Body)
    78  	if err := decoder.Decode(&v); err != nil {
    79  		respondWithError(w, r, "HandleEdit: unmarshal",
    80  			v1.UserErrorReply{
    81  				ErrorCode: v1.ErrorCodeInputInvalid,
    82  			})
    83  		return
    84  	}
    85  
    86  	u, err := c.sessions.GetSessionUser(w, r)
    87  	if err != nil {
    88  		respondWithError(w, r,
    89  			"HandleEdit: GetSessionUser: %v", err)
    90  		return
    91  	}
    92  
    93  	vr, err := c.processEdit(r.Context(), v, *u)
    94  	if err != nil {
    95  		respondWithError(w, r,
    96  			"HandleEdit: processEdit: %v", err)
    97  		return
    98  	}
    99  
   100  	util.RespondWithJSON(w, http.StatusOK, vr)
   101  }
   102  
   103  // HandleVote is the request handler for the comments v1 Vote route.
   104  func (c *Comments) HandleVote(w http.ResponseWriter, r *http.Request) {
   105  	log.Tracef("HandleVote")
   106  
   107  	var v v1.Vote
   108  	decoder := json.NewDecoder(r.Body)
   109  	if err := decoder.Decode(&v); err != nil {
   110  		respondWithError(w, r, "HandleVote: unmarshal",
   111  			v1.UserErrorReply{
   112  				ErrorCode: v1.ErrorCodeInputInvalid,
   113  			})
   114  		return
   115  	}
   116  
   117  	u, err := c.sessions.GetSessionUser(w, r)
   118  	if err != nil {
   119  		respondWithError(w, r,
   120  			"HandleVote: GetSessionUser: %v", err)
   121  		return
   122  	}
   123  
   124  	vr, err := c.processVote(r.Context(), v, *u)
   125  	if err != nil {
   126  		respondWithError(w, r,
   127  			"HandleVote: processVote: %v", err)
   128  		return
   129  	}
   130  
   131  	util.RespondWithJSON(w, http.StatusOK, vr)
   132  }
   133  
   134  // HandleDel is the request handler for the comments v1 Del route.
   135  func (c *Comments) HandleDel(w http.ResponseWriter, r *http.Request) {
   136  	log.Tracef("HandleDel")
   137  
   138  	var d v1.Del
   139  	decoder := json.NewDecoder(r.Body)
   140  	if err := decoder.Decode(&d); err != nil {
   141  		respondWithError(w, r, "HandleDel: unmarshal",
   142  			v1.UserErrorReply{
   143  				ErrorCode: v1.ErrorCodeInputInvalid,
   144  			})
   145  		return
   146  	}
   147  
   148  	u, err := c.sessions.GetSessionUser(w, r)
   149  	if err != nil {
   150  		respondWithError(w, r,
   151  			"HandleDel: GetSessionUser: %v", err)
   152  		return
   153  	}
   154  
   155  	dr, err := c.processDel(r.Context(), d, *u)
   156  	if err != nil {
   157  		respondWithError(w, r,
   158  			"HandleDel: processDel: %v", err)
   159  		return
   160  	}
   161  
   162  	util.RespondWithJSON(w, http.StatusOK, dr)
   163  }
   164  
   165  // HandleCount is the request handler for the comments v1 Count route.
   166  func (c *Comments) HandleCount(w http.ResponseWriter, r *http.Request) {
   167  	log.Tracef("HandleCount")
   168  
   169  	var ct v1.Count
   170  	decoder := json.NewDecoder(r.Body)
   171  	if err := decoder.Decode(&ct); err != nil {
   172  		respondWithError(w, r, "HandleCount: unmarshal",
   173  			v1.UserErrorReply{
   174  				ErrorCode: v1.ErrorCodeInputInvalid,
   175  			})
   176  		return
   177  	}
   178  
   179  	cr, err := c.processCount(r.Context(), ct)
   180  	if err != nil {
   181  		respondWithError(w, r,
   182  			"HandleCount: processCount: %v", err)
   183  		return
   184  	}
   185  
   186  	util.RespondWithJSON(w, http.StatusOK, cr)
   187  }
   188  
   189  // HandleComments is the request handler for the comments v1 Comments route.
   190  func (c *Comments) HandleComments(w http.ResponseWriter, r *http.Request) {
   191  	log.Tracef("HandleComments")
   192  
   193  	var cs v1.Comments
   194  	decoder := json.NewDecoder(r.Body)
   195  	if err := decoder.Decode(&cs); err != nil {
   196  		respondWithError(w, r, "HandleComments: unmarshal",
   197  			v1.UserErrorReply{
   198  				ErrorCode: v1.ErrorCodeInputInvalid,
   199  			})
   200  		return
   201  	}
   202  
   203  	// Lookup session user. This is a public route so a session may not
   204  	// exist. Ignore any session not found errors.
   205  	u, err := c.sessions.GetSessionUser(w, r)
   206  	if err != nil && err != sessions.ErrSessionNotFound {
   207  		respondWithError(w, r,
   208  			"HandleComments: GetSessionUser: %v", err)
   209  		return
   210  	}
   211  
   212  	cr, err := c.processComments(r.Context(), cs, u)
   213  	if err != nil {
   214  		respondWithError(w, r,
   215  			"HandleComments: processComments: %v", err)
   216  		return
   217  	}
   218  
   219  	util.RespondWithJSON(w, http.StatusOK, cr)
   220  }
   221  
   222  // HandleVotes is the request handler for the comments v1 Votes route.
   223  func (c *Comments) HandleVotes(w http.ResponseWriter, r *http.Request) {
   224  	log.Tracef("HandleVotes")
   225  
   226  	var v v1.Votes
   227  	decoder := json.NewDecoder(r.Body)
   228  	if err := decoder.Decode(&v); err != nil {
   229  		respondWithError(w, r, "HandleVotes: unmarshal",
   230  			v1.UserErrorReply{
   231  				ErrorCode: v1.ErrorCodeInputInvalid,
   232  			})
   233  		return
   234  	}
   235  
   236  	vr, err := c.processVotes(r.Context(), v)
   237  	if err != nil {
   238  		respondWithError(w, r,
   239  			"HandleVotes: processVotes: %v", err)
   240  		return
   241  	}
   242  
   243  	util.RespondWithJSON(w, http.StatusOK, vr)
   244  }
   245  
   246  // HandleTimestamps is the request handler for the comments v1 Timestamps
   247  // route.
   248  func (c *Comments) HandleTimestamps(w http.ResponseWriter, r *http.Request) {
   249  	log.Tracef("HandleTimestamps")
   250  
   251  	var t v1.Timestamps
   252  	decoder := json.NewDecoder(r.Body)
   253  	if err := decoder.Decode(&t); err != nil {
   254  		respondWithError(w, r, "HandleTimestamps: unmarshal",
   255  			v1.UserErrorReply{
   256  				ErrorCode: v1.ErrorCodeInputInvalid,
   257  			})
   258  		return
   259  	}
   260  
   261  	// Lookup session user. This is a public route so a session may not
   262  	// exist. Ignore any session not found errors.
   263  	u, err := c.sessions.GetSessionUser(w, r)
   264  	if err != nil && err != sessions.ErrSessionNotFound {
   265  		respondWithError(w, r,
   266  			"HandleTimestamps: GetSessionUser: %v", err)
   267  		return
   268  	}
   269  
   270  	isAdmin := u != nil && u.Admin
   271  	tr, err := c.processTimestamps(r.Context(), t, isAdmin)
   272  	if err != nil {
   273  		respondWithError(w, r,
   274  			"HandleTimestamps: processTimestamps: %v", err)
   275  		return
   276  	}
   277  
   278  	util.RespondWithJSON(w, http.StatusOK, tr)
   279  }
   280  
   281  // New returns a new Comments context.
   282  func New(cfg *config.Config, pdc *pdclient.Client, udb user.Database, s *sessions.Sessions, e *events.Manager, plugins []pdv2.Plugin) (*Comments, error) {
   283  	// Parse plugin settings
   284  	var (
   285  		lengthMax          uint32
   286  		voteChangesMax     uint32
   287  		allowExtraData     bool
   288  		votesPageSize      uint32
   289  		countPageSize      uint32
   290  		timestampsPageSize uint32
   291  		allowEdits         bool
   292  		editPeriod         uint32
   293  	)
   294  	for _, p := range plugins {
   295  		if p.ID != comments.PluginID {
   296  			// Not the comments plugin; skip
   297  			continue
   298  		}
   299  		for _, v := range p.Settings {
   300  			switch v.Key {
   301  			case comments.SettingKeyCommentLengthMax:
   302  				u, err := strconv.ParseUint(v.Value, 10, 64)
   303  				if err != nil {
   304  					return nil, err
   305  				}
   306  				lengthMax = uint32(u)
   307  
   308  			case comments.SettingKeyVoteChangesMax:
   309  				u, err := strconv.ParseUint(v.Value, 10, 64)
   310  				if err != nil {
   311  					return nil, err
   312  				}
   313  				voteChangesMax = uint32(u)
   314  
   315  			case comments.SettingKeyAllowExtraData:
   316  				b, err := strconv.ParseBool(v.Value)
   317  				if err != nil {
   318  					return nil, err
   319  				}
   320  				allowExtraData = b
   321  
   322  			case comments.SettingKeyVotesPageSize:
   323  				u, err := strconv.ParseUint(v.Value, 10, 64)
   324  				if err != nil {
   325  					return nil, err
   326  				}
   327  				votesPageSize = uint32(u)
   328  
   329  			case comments.SettingKeyCountPageSize:
   330  				u, err := strconv.ParseUint(v.Value, 10, 64)
   331  				if err != nil {
   332  					return nil, err
   333  				}
   334  				countPageSize = uint32(u)
   335  
   336  			case comments.SettingKeyTimestampsPageSize:
   337  				u, err := strconv.ParseUint(v.Value, 10, 64)
   338  				if err != nil {
   339  					return nil, err
   340  				}
   341  				timestampsPageSize = uint32(u)
   342  
   343  			case comments.SettingKeyAllowEdits:
   344  				b, err := strconv.ParseBool(v.Value)
   345  				if err != nil {
   346  					return nil, errors.Errorf("invalid plugin setting %v '%v': %v",
   347  						v.Key, v.Value, err)
   348  				}
   349  				allowEdits = b
   350  
   351  			case comments.SettingKeyEditPeriod:
   352  				u, err := strconv.ParseUint(v.Value, 10, 64)
   353  				if err != nil {
   354  					return nil, errors.Errorf("invalid plugin setting %v '%v': %v",
   355  						v.Key, v.Value, err)
   356  				}
   357  				editPeriod = uint32(u)
   358  
   359  			default:
   360  				// Skip unknown settings
   361  				log.Warnf("Unknown plugin setting %v; Skipping...", v.Key)
   362  			}
   363  		}
   364  	}
   365  
   366  	// Verify all plugin settings have been provided
   367  	switch {
   368  	case lengthMax == 0:
   369  		return nil, errors.Errorf("plugin setting not found: %v",
   370  			comments.SettingKeyCommentLengthMax)
   371  	case voteChangesMax == 0:
   372  		return nil, errors.Errorf("plugin setting not found: %v",
   373  			comments.SettingKeyVoteChangesMax)
   374  	case votesPageSize == 0:
   375  		return nil, errors.Errorf("plugin setting not found: %v",
   376  			comments.SettingKeyVotesPageSize)
   377  	case countPageSize == 0:
   378  		return nil, errors.Errorf("plugin setting not found: %v",
   379  			comments.SettingKeyCountPageSize)
   380  	case timestampsPageSize == 0:
   381  		return nil, errors.Errorf("plugin setting not found: %v",
   382  			comments.SettingKeyTimestampsPageSize)
   383  	case editPeriod == 0:
   384  		return nil, errors.Errorf("plugin setting not found: %v",
   385  			comments.SettingKeyEditPeriod)
   386  	}
   387  
   388  	return &Comments{
   389  		cfg:       cfg,
   390  		politeiad: pdc,
   391  		userdb:    udb,
   392  		sessions:  s,
   393  		events:    e,
   394  		policy: &v1.PolicyReply{
   395  			LengthMax:          lengthMax,
   396  			VoteChangesMax:     voteChangesMax,
   397  			AllowExtraData:     allowExtraData,
   398  			VotesPageSize:      votesPageSize,
   399  			CountPageSize:      countPageSize,
   400  			TimestampsPageSize: timestampsPageSize,
   401  			AllowEdits:         allowEdits,
   402  			EditPeriod:         editPeriod,
   403  		},
   404  	}, nil
   405  }