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 }