github.com/decred/politeia@v1.4.0/politeiawww/legacy/user/localdb/cms.go (about)

     1  // Copyright (c) 2020 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 localdb
     6  
     7  import (
     8  	"strings"
     9  
    10  	"github.com/decred/politeia/politeiawww/legacy/user"
    11  	"github.com/syndtr/goleveldb/leveldb/util"
    12  )
    13  
    14  const (
    15  	cmsUserPrefix      = "cmswww"
    16  	cmsCodeStatsPrefix = "codestats"
    17  )
    18  
    19  // isCMSUserRecord returns true if the given key is a cms user record,
    20  // and false otherwise. This is helpful when iterating the user records
    21  // because the DB contains some non-user records.
    22  func isCMSUserRecord(key string) bool {
    23  	return strings.HasPrefix(key, cmsUserPrefix)
    24  }
    25  
    26  // isCMSUserRecord returns true if the given key is a cms user record,
    27  // and false otherwise. This is helpful when iterating the user records
    28  // because the DB contains some non-user records.
    29  func isCMSCodeStatsRecord(key string) bool {
    30  	return strings.HasPrefix(key, cmsCodeStatsPrefix)
    31  }
    32  
    33  // cmdNewCMSUser inserts a new CMSUser record into the database.
    34  func (l *localdb) cmdNewCMSUser(payload string) (string, error) {
    35  	// Decode payload
    36  	nu, err := user.DecodeNewCMSUser([]byte(payload))
    37  	if err != nil {
    38  		return "", err
    39  	}
    40  
    41  	if l.shutdown {
    42  		return "", user.ErrShutdown
    43  	}
    44  
    45  	log.Debugf("cmdNewCMSUser: %v", nu.Email)
    46  
    47  	// Create a new User record
    48  	u := user.User{
    49  		Email:                     nu.Email,
    50  		Username:                  nu.Username,
    51  		NewUserVerificationToken:  nu.NewUserVerificationToken,
    52  		NewUserVerificationExpiry: nu.NewUserVerificationExpiry,
    53  	}
    54  	err = l.UserNew(u)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  
    59  	// Get user that we just created to get the ID and other User stuff set
    60  	setUser, err := l.UserGet(nu.Email)
    61  	if err != nil {
    62  		return "", err
    63  	}
    64  
    65  	l.Lock()
    66  	defer l.Unlock()
    67  
    68  	cmsUser := user.CMSUser{
    69  		User:           *setUser,
    70  		ContractorType: nu.ContractorType,
    71  	}
    72  	key := []byte(cmsUserPrefix + cmsUser.Email)
    73  
    74  	// Make sure cms user does not exist
    75  	ok, err := l.userdb.Has(key, nil)
    76  	if err != nil {
    77  		return "", err
    78  	} else if ok {
    79  		return "", user.ErrUserExists
    80  	}
    81  
    82  	cmsPayload, err := user.EncodeCMSUser(cmsUser)
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  
    87  	err = l.userdb.Put(key, cmsPayload, nil)
    88  	if err != nil {
    89  		return "", err
    90  	}
    91  
    92  	// Prepare reply
    93  	var nur user.NewCMSUserReply
    94  	reply, err := user.EncodeNewCMSUserReply(nur)
    95  	if err != nil {
    96  		return "", nil
    97  	}
    98  
    99  	return string(reply), nil
   100  }
   101  
   102  // updateCMSUser updates an existing CMSUser record with the provided user
   103  // info.
   104  func (l *localdb) updateCMSUser(nu user.UpdateCMSUser) error {
   105  	if l.shutdown {
   106  		return user.ErrShutdown
   107  	}
   108  
   109  	log.Debugf("updateCMSUser: %v", nu.ID)
   110  
   111  	// Get user that we just created to get the ID and other User stuff set
   112  	setUser, err := l.UserGetById(nu.ID)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	l.Lock()
   118  	defer l.Unlock()
   119  
   120  	u := user.CMSUser{
   121  		User:               *setUser,
   122  		Domain:             nu.Domain,
   123  		ContractorName:     nu.ContractorName,
   124  		ContractorType:     nu.ContractorType,
   125  		ContractorContact:  nu.ContractorContact,
   126  		ContractorLocation: nu.ContractorLocation,
   127  		GitHubName:         nu.GitHubName,
   128  		MatrixName:         nu.MatrixName,
   129  		ProposalsOwned:     nu.ProposalsOwned,
   130  		SupervisorUserIDs:  nu.SupervisorUserIDs,
   131  	}
   132  	key := []byte(cmsUserPrefix + setUser.Email)
   133  
   134  	// Make sure user already exists
   135  	exists, err := l.userdb.Has(key, nil)
   136  	if err != nil {
   137  		return err
   138  	} else if !exists {
   139  		return user.ErrUserNotFound
   140  	}
   141  
   142  	payload, err := user.EncodeCMSUser(u)
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	return l.userdb.Put(key, payload, nil)
   148  }
   149  
   150  // cmdUpdateCMSUser updates an existing CMSUser record in the database.
   151  func (l *localdb) cmdUpdateCMSUser(payload string) (string, error) {
   152  	// Decode payload
   153  	uu, err := user.DecodeUpdateCMSUser([]byte(payload))
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  
   158  	err = l.updateCMSUser(*uu)
   159  	if err != nil {
   160  		return "", err
   161  	}
   162  
   163  	// Prepare reply
   164  	var uur user.UpdateCMSUserReply
   165  	reply, err := user.EncodeUpdateCMSUserReply(uur)
   166  	if err != nil {
   167  		return "", nil
   168  	}
   169  
   170  	return string(reply), nil
   171  }
   172  
   173  // cmdCMSUserByID returns the user information for a given user ID.
   174  func (l *localdb) cmdCMSUserByID(payload string) (string, error) {
   175  	// Decode payload
   176  	p, err := user.DecodeCMSUserByID([]byte(payload))
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  
   181  	l.RLock()
   182  	defer l.RUnlock()
   183  
   184  	if l.shutdown {
   185  		return "", user.ErrShutdown
   186  	}
   187  
   188  	log.Debugf("cmsCMSUserById")
   189  
   190  	iter := l.userdb.NewIterator(util.BytesPrefix([]byte(cmsUserPrefix)), nil)
   191  	for iter.Next() {
   192  		key := iter.Key()
   193  		value := iter.Value()
   194  
   195  		if !isCMSUserRecord(string(key)) {
   196  			continue
   197  		}
   198  
   199  		u, err := user.DecodeCMSUser(value)
   200  		if err != nil {
   201  			return "", err
   202  		}
   203  
   204  		if u.ID.String() == p.ID {
   205  			r := user.CMSUserByIDReply{
   206  				User: u,
   207  			}
   208  			reply, err := user.EncodeCMSUserByIDReply(r)
   209  			if err != nil {
   210  				return "", err
   211  			}
   212  
   213  			return string(reply), nil
   214  		}
   215  	}
   216  	iter.Release()
   217  
   218  	if iter.Error() != nil {
   219  		return "", iter.Error()
   220  	}
   221  
   222  	return "", user.ErrUserNotFound
   223  }
   224  
   225  // cmdNewCMSCodeStats inserts a new CMSUser record into the database.
   226  func (l *localdb) cmdNewCMSCodeStats(payload string) (string, error) {
   227  	// Decode payload
   228  	nu, err := user.DecodeNewCMSCodeStats([]byte(payload))
   229  	if err != nil {
   230  		return "", err
   231  	}
   232  
   233  	l.Lock()
   234  	defer l.Unlock()
   235  
   236  	if l.shutdown {
   237  		return "", user.ErrShutdown
   238  	}
   239  
   240  	for _, ncs := range nu.UserCodeStats {
   241  		key := []byte(cmsCodeStatsPrefix + ncs.ID)
   242  
   243  		// Make sure cms user does not exist
   244  		ok, err := l.userdb.Has(key, nil)
   245  		if err != nil {
   246  			return "", err
   247  		} else if ok {
   248  			return "", user.ErrUserExists
   249  		}
   250  
   251  		cmsPayload, err := user.EncodeCodeStats(ncs)
   252  		if err != nil {
   253  			return "", err
   254  		}
   255  
   256  		err = l.userdb.Put(key, cmsPayload, nil)
   257  		if err != nil {
   258  			return "", err
   259  		}
   260  	}
   261  
   262  	// Prepare reply
   263  	var nur user.NewCMSCodeStatsReply
   264  	reply, err := user.EncodeNewCMSCodeStatsReply(nur)
   265  	if err != nil {
   266  		return "", nil
   267  	}
   268  
   269  	return string(reply), nil
   270  }
   271  
   272  // cmdUpdateCMSCodeStats updates an existing CMSUser record into the database.
   273  func (l *localdb) cmdUpdateCMSCodeStats(payload string) (string, error) {
   274  	// Decode payload
   275  	ucs, err := user.DecodeUpdateCMSCodeStats([]byte(payload))
   276  	if err != nil {
   277  		return "", err
   278  	}
   279  
   280  	l.Lock()
   281  	defer l.Unlock()
   282  
   283  	if l.shutdown {
   284  		return "", user.ErrShutdown
   285  	}
   286  
   287  	for _, ncs := range ucs.UserCodeStats {
   288  		key := []byte(cmsCodeStatsPrefix + ncs.ID)
   289  
   290  		exists, err := l.userdb.Has(key, nil)
   291  		if err != nil {
   292  			return "", err
   293  		} else if !exists {
   294  			return "", user.ErrCodeStatsNotFound
   295  		}
   296  
   297  		cmsPayload, err := user.EncodeCodeStats(ncs)
   298  		if err != nil {
   299  			return "", err
   300  		}
   301  
   302  		err = l.userdb.Put(key, cmsPayload, nil)
   303  		if err != nil {
   304  			return "", err
   305  		}
   306  	}
   307  	// Prepare reply
   308  	var nur user.UpdateCMSCodeStatsReply
   309  	reply, err := user.EncodeUpdateCMSCodeStatsReply(nur)
   310  	if err != nil {
   311  		return "", nil
   312  	}
   313  
   314  	return string(reply), nil
   315  }
   316  
   317  func (l *localdb) cmdCMSCodeStatsByUserMonthYear(payload string) (string, error) {
   318  	// Decode payload
   319  	p, err := user.DecodeCMSCodeStatsByUserMonthYear([]byte(payload))
   320  	if err != nil {
   321  		return "", err
   322  	}
   323  
   324  	l.RLock()
   325  	defer l.RUnlock()
   326  
   327  	if l.shutdown {
   328  		return "", user.ErrShutdown
   329  	}
   330  
   331  	iter := l.userdb.NewIterator(util.BytesPrefix([]byte(cmsCodeStatsPrefix)), nil)
   332  	matching := make([]user.CodeStats, 0, 1048) // PNOOMA
   333  	for iter.Next() {
   334  		key := iter.Key()
   335  		value := iter.Value()
   336  
   337  		if !isCMSCodeStatsRecord(string(key)) {
   338  			continue
   339  		}
   340  
   341  		cs, err := user.DecodeCodeStats(value)
   342  		if err != nil {
   343  			return "", err
   344  		}
   345  		if cs.GitHubName == p.GithubName &&
   346  			p.Month == cs.Month &&
   347  			cs.Year == p.Year {
   348  			matching = append(matching, *cs)
   349  		}
   350  	}
   351  	iter.Release()
   352  
   353  	if iter.Error() != nil {
   354  		return "", iter.Error()
   355  	}
   356  	r := user.CMSCodeStatsByUserMonthYearReply{
   357  		UserCodeStats: matching,
   358  	}
   359  	reply, err := user.EncodeCMSCodeStatsByUserMonthYearReply(r)
   360  	if err != nil {
   361  		return "", err
   362  	}
   363  
   364  	return string(reply), nil
   365  }
   366  
   367  // Exec executes a cms plugin command.
   368  func (l *localdb) cmsPluginExec(cmd, payload string) (string, error) {
   369  	switch cmd {
   370  	case user.CmdNewCMSUser:
   371  		return l.cmdNewCMSUser(payload)
   372  	case user.CmdUpdateCMSUser:
   373  		return l.cmdUpdateCMSUser(payload)
   374  	case user.CmdCMSUserByID:
   375  		return l.cmdCMSUserByID(payload)
   376  	case user.CmdNewCMSUserCodeStats:
   377  		return l.cmdNewCMSCodeStats(payload)
   378  	case user.CmdUpdateCMSUserCodeStats:
   379  		return l.cmdUpdateCMSCodeStats(payload)
   380  	case user.CmdCMSCodeStatsByUserMonthYear:
   381  		return l.cmdCMSCodeStatsByUserMonthYear(payload)
   382  	default:
   383  		return "", user.ErrInvalidPluginCmd
   384  	}
   385  }