github.com/decred/politeia@v1.4.0/politeiawww/legacy/pi/pi.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 pi
     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/pi"
    15  	v1 "github.com/decred/politeia/politeiawww/api/pi/v1"
    16  	"github.com/decred/politeia/politeiawww/config"
    17  	"github.com/decred/politeia/politeiawww/legacy/events"
    18  	"github.com/decred/politeia/politeiawww/legacy/mail"
    19  	"github.com/decred/politeia/politeiawww/legacy/sessions"
    20  	"github.com/decred/politeia/politeiawww/legacy/user"
    21  	"github.com/decred/politeia/util"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  // Pi is the context for the pi API.
    26  type Pi struct {
    27  	cfg       *config.Config
    28  	politeiad *pdclient.Client
    29  	userdb    user.Database
    30  	mail      mail.Mailer
    31  	sessions  *sessions.Sessions
    32  	events    *events.Manager
    33  	policy    *v1.PolicyReply
    34  }
    35  
    36  // HandlePolicy is the request handler for the pi v1 Policy route.
    37  func (p *Pi) HandlePolicy(w http.ResponseWriter, r *http.Request) {
    38  	log.Tracef("HandlePolicy")
    39  
    40  	util.RespondWithJSON(w, http.StatusOK, p.policy)
    41  }
    42  
    43  // HandleSetBillingStatus is the request handler for the pi v1 BillingStatus
    44  // route.
    45  func (p *Pi) HandleSetBillingStatus(w http.ResponseWriter, r *http.Request) {
    46  	log.Tracef("HandleSetBillingStatus")
    47  
    48  	var sbs v1.SetBillingStatus
    49  	decoder := json.NewDecoder(r.Body)
    50  	if err := decoder.Decode(&sbs); err != nil {
    51  		respondWithError(w, r, "HandleSetBillingStatus: unmarshal",
    52  			v1.UserErrorReply{
    53  				ErrorCode: v1.ErrorCodeInputInvalid,
    54  			})
    55  		return
    56  	}
    57  
    58  	u, err := p.sessions.GetSessionUser(w, r)
    59  	if err != nil {
    60  		respondWithError(w, r,
    61  			"HandleSetBillingStatus: GetSessionUser: %v", err)
    62  		return
    63  	}
    64  
    65  	bsr, err := p.processSetBillingStatus(r.Context(), sbs, *u)
    66  	if err != nil {
    67  		respondWithError(w, r,
    68  			"HandleSetBillingStatus: processSetBillingStatus: %v", err)
    69  		return
    70  	}
    71  
    72  	util.RespondWithJSON(w, http.StatusOK, bsr)
    73  }
    74  
    75  // HandleBillingStatusChanges is the request handler for the pi v1
    76  // BillingStatusChanges route.
    77  func (p *Pi) HandleBillingStatusChanges(w http.ResponseWriter, r *http.Request) {
    78  	log.Tracef("HandleBillingStatusChanges")
    79  
    80  	var bscs v1.BillingStatusChanges
    81  	decoder := json.NewDecoder(r.Body)
    82  	if err := decoder.Decode(&bscs); err != nil {
    83  		respondWithError(w, r, "HandleBillingStatusChanges: unmarshal",
    84  			v1.UserErrorReply{
    85  				ErrorCode: v1.ErrorCodeInputInvalid,
    86  			})
    87  		return
    88  	}
    89  
    90  	bsr, err := p.processBillingStatusChanges(r.Context(), bscs)
    91  	if err != nil {
    92  		respondWithError(w, r,
    93  			"HandleBillingStatusChanges: processBillingStatusChanges: %v", err)
    94  		return
    95  	}
    96  
    97  	util.RespondWithJSON(w, http.StatusOK, bsr)
    98  
    99  }
   100  
   101  // HandleSummaries is the request handler for the pi v1 Summaries route.
   102  func (p *Pi) HandleSummaries(w http.ResponseWriter, r *http.Request) {
   103  	log.Tracef("HandleSummaries")
   104  
   105  	var s v1.Summaries
   106  	decoder := json.NewDecoder(r.Body)
   107  	if err := decoder.Decode(&s); err != nil {
   108  		respondWithError(w, r, "HandleSummaries: unmarshal",
   109  			v1.UserErrorReply{
   110  				ErrorCode: v1.ErrorCodeInputInvalid,
   111  			})
   112  		return
   113  	}
   114  
   115  	bsr, err := p.processSummaries(r.Context(), s)
   116  	if err != nil {
   117  		respondWithError(w, r,
   118  			"HandleSummaries: processSummaries: %v", err)
   119  		return
   120  	}
   121  
   122  	util.RespondWithJSON(w, http.StatusOK, bsr)
   123  }
   124  
   125  // New returns a new Pi context.
   126  func New(cfg *config.Config, pdc *pdclient.Client, udb user.Database, m mail.Mailer, s *sessions.Sessions, e *events.Manager, plugins []pdv2.Plugin) (*Pi, error) {
   127  	// Parse plugin settings
   128  	var (
   129  		textFileSizeMax              uint32
   130  		imageFileCountMax            uint32
   131  		imageFileSizeMax             uint32
   132  		titleLengthMin               uint32
   133  		titleLengthMax               uint32
   134  		titleSupportedChars          []string
   135  		amountMin                    uint64
   136  		amountMax                    uint64
   137  		startDateMin                 int64
   138  		endDateMax                   int64
   139  		domains                      []string
   140  		billingStatusChangesMax      uint32
   141  		summariesPageSize            uint32
   142  		billingStatusChangesPageSize uint32
   143  	)
   144  	for _, p := range plugins {
   145  		if p.ID != pi.PluginID {
   146  			// Not the pi plugin; skip
   147  			continue
   148  		}
   149  		for _, v := range p.Settings {
   150  			switch v.Key {
   151  			case pi.SettingKeyTextFileSizeMax:
   152  				u, err := strconv.ParseUint(v.Value, 10, 64)
   153  				if err != nil {
   154  					return nil, err
   155  				}
   156  				textFileSizeMax = uint32(u)
   157  
   158  			case pi.SettingKeyImageFileCountMax:
   159  				u, err := strconv.ParseUint(v.Value, 10, 64)
   160  				if err != nil {
   161  					return nil, err
   162  				}
   163  				imageFileCountMax = uint32(u)
   164  
   165  			case pi.SettingKeyImageFileSizeMax:
   166  				u, err := strconv.ParseUint(v.Value, 10, 64)
   167  				if err != nil {
   168  					return nil, err
   169  				}
   170  				imageFileSizeMax = uint32(u)
   171  
   172  			case pi.SettingKeyTitleLengthMin:
   173  				u, err := strconv.ParseUint(v.Value, 10, 64)
   174  				if err != nil {
   175  					return nil, err
   176  				}
   177  				titleLengthMin = uint32(u)
   178  
   179  			case pi.SettingKeyTitleLengthMax:
   180  				u, err := strconv.ParseUint(v.Value, 10, 64)
   181  				if err != nil {
   182  					return nil, err
   183  				}
   184  				titleLengthMax = uint32(u)
   185  
   186  			case pi.SettingKeyTitleSupportedChars:
   187  				err := json.Unmarshal([]byte(v.Value), &titleSupportedChars)
   188  				if err != nil {
   189  					return nil, err
   190  				}
   191  
   192  			case pi.SettingKeyProposalAmountMin:
   193  				u, err := strconv.ParseUint(v.Value, 10, 64)
   194  				if err != nil {
   195  					return nil, err
   196  				}
   197  				amountMin = u
   198  
   199  			case pi.SettingKeyProposalAmountMax:
   200  				u, err := strconv.ParseUint(v.Value, 10, 64)
   201  				if err != nil {
   202  					return nil, err
   203  				}
   204  				amountMax = u
   205  
   206  			case pi.SettingKeyProposalStartDateMin:
   207  				u, err := strconv.ParseInt(v.Value, 10, 64)
   208  				if err != nil {
   209  					return nil, err
   210  				}
   211  				startDateMin = u
   212  
   213  			case pi.SettingKeyProposalEndDateMax:
   214  				u, err := strconv.ParseInt(v.Value, 10, 64)
   215  				if err != nil {
   216  					return nil, err
   217  				}
   218  				endDateMax = u
   219  
   220  			case pi.SettingKeyProposalDomains:
   221  				err := json.Unmarshal([]byte(v.Value), &domains)
   222  				if err != nil {
   223  					return nil, err
   224  				}
   225  				// Ensure no empty strings.
   226  				for _, d := range domains {
   227  					if d == "" {
   228  						return nil, errors.Errorf("proposal domain can not be an empty " +
   229  							"string")
   230  					}
   231  				}
   232  
   233  			case pi.SettingKeyBillingStatusChangesMax:
   234  				u, err := strconv.ParseUint(v.Value, 10, 64)
   235  				if err != nil {
   236  					return nil, err
   237  				}
   238  				billingStatusChangesMax = uint32(u)
   239  
   240  			case pi.SettingKeySummariesPageSize:
   241  				u, err := strconv.ParseUint(v.Value, 10, 64)
   242  				if err != nil {
   243  					return nil, err
   244  				}
   245  				summariesPageSize = uint32(u)
   246  
   247  			case pi.SettingKeyBillingStatusChangesPageSize:
   248  				u, err := strconv.ParseUint(v.Value, 10, 64)
   249  				if err != nil {
   250  					return nil, err
   251  				}
   252  				billingStatusChangesPageSize = uint32(u)
   253  
   254  			default:
   255  				// Skip unknown settings
   256  				log.Warnf("Unknown plugin setting %v; Skipping...", v.Key)
   257  			}
   258  		}
   259  	}
   260  
   261  	// Verify all plugin settings have been provided
   262  	switch {
   263  	case textFileSizeMax == 0:
   264  		return nil, errors.Errorf("plugin setting not found: %v",
   265  			pi.SettingKeyTextFileSizeMax)
   266  	case imageFileCountMax == 0:
   267  		return nil, errors.Errorf("plugin setting not found: %v",
   268  			pi.SettingKeyImageFileCountMax)
   269  	case imageFileSizeMax == 0:
   270  		return nil, errors.Errorf("plugin setting not found: %v",
   271  			pi.SettingKeyImageFileSizeMax)
   272  	case titleLengthMin == 0:
   273  		return nil, errors.Errorf("plugin setting not found: %v",
   274  			pi.SettingKeyTitleLengthMin)
   275  	case titleLengthMax == 0:
   276  		return nil, errors.Errorf("plugin setting not found: %v",
   277  			pi.SettingKeyTitleLengthMax)
   278  	case len(titleSupportedChars) == 0:
   279  		return nil, errors.Errorf("plugin setting not found: %v",
   280  			pi.SettingKeyTitleSupportedChars)
   281  	case amountMin == 0:
   282  		return nil, errors.Errorf("plugin setting not found: %v",
   283  			pi.SettingKeyProposalAmountMin)
   284  	case amountMax == 0:
   285  		return nil, errors.Errorf("plugin setting not found: %v",
   286  			pi.SettingKeyProposalAmountMax)
   287  	case endDateMax == 0:
   288  		return nil, errors.Errorf("plugin setting not found: %v",
   289  			pi.SettingKeyProposalEndDateMax)
   290  	case len(domains) == 0:
   291  		return nil, errors.Errorf("plugin setting not found: %v",
   292  			pi.SettingKeyProposalDomains)
   293  	case summariesPageSize == 0:
   294  		return nil, errors.Errorf("plugin setting not found: %v",
   295  			pi.SettingKeySummariesPageSize)
   296  	case billingStatusChangesPageSize == 0:
   297  		return nil, errors.Errorf("plugin setting not found: %v",
   298  			pi.SettingKeyBillingStatusChangesPageSize)
   299  	}
   300  
   301  	// Setup pi context
   302  	p := Pi{
   303  		cfg:       cfg,
   304  		politeiad: pdc,
   305  		userdb:    udb,
   306  		sessions:  s,
   307  		events:    e,
   308  		mail:      m,
   309  		policy: &v1.PolicyReply{
   310  			TextFileSizeMax:              textFileSizeMax,
   311  			ImageFileCountMax:            imageFileCountMax,
   312  			ImageFileSizeMax:             imageFileSizeMax,
   313  			NameLengthMin:                titleLengthMin,
   314  			NameLengthMax:                titleLengthMax,
   315  			NameSupportedChars:           titleSupportedChars,
   316  			AmountMin:                    amountMin,
   317  			AmountMax:                    amountMax,
   318  			StartDateMin:                 startDateMin,
   319  			EndDateMax:                   endDateMax,
   320  			Domains:                      domains,
   321  			SummariesPageSize:            summariesPageSize,
   322  			BillingStatusChangesPageSize: billingStatusChangesPageSize,
   323  			BillingStatusChangesMax:      billingStatusChangesMax,
   324  		},
   325  	}
   326  
   327  	// Setup event listeners
   328  	p.setupEventListeners()
   329  
   330  	return &p, nil
   331  }