2017-11-10 03:33:11 +00:00
package common
2017-06-13 07:12:58 +00:00
2017-08-06 15:22:18 +00:00
import (
2017-09-03 04:50:31 +00:00
"database/sql"
2017-08-06 15:22:18 +00:00
"errors"
"strconv"
2018-10-27 03:21:02 +00:00
"github.com/Azareal/Gosora/query_gen"
2017-08-06 15:22:18 +00:00
"golang.org/x/crypto/bcrypt"
)
2017-06-13 07:12:58 +00:00
2017-09-10 16:57:22 +00:00
// TODO: Add the watchdog goroutine
2017-09-15 22:20:01 +00:00
// TODO: Add some sort of update method
2017-11-11 04:06:16 +00:00
var Users UserStore
var ErrAccountExists = errors . New ( "this username is already in use" )
2018-03-17 08:16:43 +00:00
var ErrLongUsername = errors . New ( "this username is too long" )
2017-06-13 07:12:58 +00:00
type UserStore interface {
2017-11-11 04:06:16 +00:00
DirtyGet ( id int ) * User
2017-06-13 07:12:58 +00:00
Get ( id int ) ( * User , error )
2018-09-20 06:31:09 +00:00
GetByName ( name string ) ( * User , error )
2017-09-15 22:20:01 +00:00
Exists ( id int ) bool
2018-05-27 09:36:35 +00:00
GetOffset ( offset int , perPage int ) ( users [ ] * User , err error )
2017-09-15 22:20:01 +00:00
//BulkGet(ids []int) ([]*User, error)
BulkGetMap ( ids [ ] int ) ( map [ int ] * User , error )
2017-06-13 07:12:58 +00:00
BypassGet ( id int ) ( * User , error )
2017-10-21 00:27:47 +00:00
Create ( username string , password string , email string , group int , active bool ) ( int , error )
2017-11-23 05:37:08 +00:00
Reload ( id int ) error
2019-06-01 12:31:48 +00:00
Count ( ) int
2017-09-15 22:20:01 +00:00
2017-11-23 05:37:08 +00:00
SetCache ( cache UserCache )
GetCache ( ) UserCache
2017-06-13 07:12:58 +00:00
}
2017-11-23 05:37:08 +00:00
type DefaultUserStore struct {
cache UserCache
2017-09-10 16:57:22 +00:00
get * sql . Stmt
2018-09-20 06:31:09 +00:00
getByName * sql . Stmt
2018-05-27 09:36:35 +00:00
getOffset * sql . Stmt
2017-09-15 22:20:01 +00:00
exists * sql . Stmt
2017-09-10 16:57:22 +00:00
register * sql . Stmt
usernameExists * sql . Stmt
2019-06-01 12:31:48 +00:00
count * sql . Stmt
2017-06-13 07:12:58 +00:00
}
2017-11-23 05:37:08 +00:00
// NewDefaultUserStore gives you a new instance of DefaultUserStore
func NewDefaultUserStore ( cache UserCache ) ( * DefaultUserStore , error ) {
2018-08-04 11:46:36 +00:00
acc := qgen . NewAcc ( )
2017-11-23 05:37:08 +00:00
if cache == nil {
cache = NewNullUserCache ( )
}
2017-11-06 07:23:32 +00:00
// TODO: Add an admin version of registerStmt with more flexibility?
2017-11-23 05:37:08 +00:00
return & DefaultUserStore {
cache : cache ,
2018-03-31 05:25:27 +00:00
get : acc . SimpleSelect ( "users" , "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group" , "uid = ?" , "" , "" ) ,
2018-09-20 06:31:09 +00:00
getByName : acc . Select ( "users" ) . Columns ( "uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group" ) . Where ( "name = ?" ) . Prepare ( ) ,
2018-05-27 09:36:35 +00:00
getOffset : acc . Select ( "users" ) . Columns ( "uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group" ) . Orderby ( "uid ASC" ) . Limit ( "?,?" ) . Prepare ( ) ,
2017-11-06 07:23:32 +00:00
exists : acc . SimpleSelect ( "users" , "uid" , "uid = ?" , "" , "" ) ,
2018-10-06 13:14:11 +00:00
register : acc . Insert ( "users" ) . Columns ( "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt" ) . Fields ( "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()" ) . Prepare ( ) , // TODO: Implement user_count on users_groups here
2017-11-06 07:23:32 +00:00
usernameExists : acc . SimpleSelect ( "users" , "name" , "name = ?" , "" , "" ) ,
2019-06-01 12:31:48 +00:00
count : acc . Count ( "users" ) . Prepare ( ) ,
2017-11-06 07:23:32 +00:00
} , acc . FirstError ( )
2017-06-13 07:12:58 +00:00
}
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) DirtyGet ( id int ) * User {
user , err := s . Get ( id )
2017-11-23 05:37:08 +00:00
if err == nil {
2017-11-11 04:06:16 +00:00
return user
}
2019-08-28 06:47:54 +00:00
/ * if s . OutOfBounds ( id ) {
2018-09-13 07:41:01 +00:00
return BlankUser ( )
} * /
2017-11-11 04:06:16 +00:00
return BlankUser ( )
}
2017-11-23 05:37:08 +00:00
// TODO: Log weird cache errors? Not just here but in every *Cache?
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) Get ( id int ) ( * User , error ) {
u , err := s . cache . Get ( id )
2017-11-23 05:37:08 +00:00
if err == nil {
2018-12-06 11:54:20 +00:00
//log.Print("cached user")
//log.Print(string(debug.Stack()))
//log.Println("")
2019-08-28 06:47:54 +00:00
return u , nil
2017-06-13 07:12:58 +00:00
}
2018-12-06 11:54:20 +00:00
//log.Print("uncached user")
2017-06-13 07:12:58 +00:00
2019-08-28 06:47:54 +00:00
u = & User { ID : id , Loggedin : true }
err = s . get . QueryRow ( id ) . Scan ( & u . Name , & u . Group , & u . Active , & u . IsSuperAdmin , & u . Session , & u . Email , & u . RawAvatar , & u . Message , & u . URLPrefix , & u . URLName , & u . Level , & u . Score , & u . Liked , & u . LastIP , & u . TempGroup )
2018-09-20 06:31:09 +00:00
if err == nil {
2019-08-28 06:47:54 +00:00
u . Init ( )
s . cache . Set ( u )
2018-09-20 06:31:09 +00:00
}
2019-08-28 06:47:54 +00:00
return u , err
2018-09-20 06:31:09 +00:00
}
// TODO: Log weird cache errors? Not just here but in every *Cache?
// ! This bypasses the cache, use frugally
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) GetByName ( name string ) ( * User , error ) {
u := & User { Loggedin : true }
err := s . getByName . QueryRow ( name ) . Scan ( & u . ID , & u . Name , & u . Group , & u . Active , & u . IsSuperAdmin , & u . Session , & u . Email , & u . RawAvatar , & u . Message , & u . URLPrefix , & u . URLName , & u . Level , & u . Score , & u . Liked , & u . LastIP , & u . TempGroup )
2017-06-13 07:12:58 +00:00
if err == nil {
2019-08-28 06:47:54 +00:00
u . Init ( )
s . cache . Set ( u )
2017-06-13 07:12:58 +00:00
}
2019-08-28 06:47:54 +00:00
return u , err
2017-06-13 07:12:58 +00:00
}
2018-05-27 09:36:35 +00:00
// TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps
// TODO: Make this a little more consistent with DefaultGroupStore's GetRange method
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) GetOffset ( offset int , perPage int ) ( users [ ] * User , err error ) {
rows , err := s . getOffset . Query ( offset , perPage )
2018-05-27 09:36:35 +00:00
if err != nil {
return users , err
}
defer rows . Close ( )
for rows . Next ( ) {
2019-08-28 06:47:54 +00:00
u := & User { Loggedin : true }
err := rows . Scan ( & u . ID , & u . Name , & u . Group , & u . Active , & u . IsSuperAdmin , & u . Session , & u . Email , & u . RawAvatar , & u . Message , & u . URLPrefix , & u . URLName , & u . Level , & u . Score , & u . Liked , & u . LastIP , & u . TempGroup )
2018-05-27 09:36:35 +00:00
if err != nil {
return nil , err
}
2019-08-28 06:47:54 +00:00
u . Init ( )
s . cache . Set ( u )
users = append ( users , u )
2018-05-27 09:36:35 +00:00
}
return users , rows . Err ( )
}
2017-09-10 16:57:22 +00:00
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TODO: ID of 0 should always error?
2019-07-25 07:00:49 +00:00
func ( s * DefaultUserStore ) BulkGetMap ( ids [ ] int ) ( list map [ int ] * User , err error ) {
2019-08-28 06:47:54 +00:00
idCount := len ( ids )
2017-08-06 15:22:18 +00:00
list = make ( map [ int ] * User )
2017-09-03 04:50:31 +00:00
if idCount == 0 {
2017-08-06 15:22:18 +00:00
return list , nil
}
2017-09-03 04:50:31 +00:00
var stillHere [ ] int
2019-07-25 07:00:49 +00:00
sliceList := s . cache . BulkGet ( ids )
2018-06-06 06:13:55 +00:00
if len ( sliceList ) > 0 {
for i , sliceItem := range sliceList {
if sliceItem != nil {
list [ sliceItem . ID ] = sliceItem
} else {
stillHere = append ( stillHere , ids [ i ] )
}
2017-08-06 15:22:18 +00:00
}
2018-06-06 06:13:55 +00:00
ids = stillHere
2017-08-06 15:22:18 +00:00
}
// If every user is in the cache, then return immediately
if len ( ids ) == 0 {
return list , nil
2018-09-13 07:41:01 +00:00
} else if len ( ids ) == 1 {
2019-07-25 07:00:49 +00:00
user , err := s . Get ( ids [ 0 ] )
2018-09-13 07:41:01 +00:00
if err != nil {
return list , err
}
2019-07-25 07:00:49 +00:00
list [ user . ID ] = user
2018-09-13 07:41:01 +00:00
return list , nil
2017-08-06 15:22:18 +00:00
}
2019-08-28 06:47:54 +00:00
// TODO: Add a function for the q stuff
var q string
2019-07-25 07:00:49 +00:00
idList := make ( [ ] interface { } , len ( ids ) )
for i , id := range ids {
idList [ i ] = strconv . Itoa ( id )
2019-08-28 06:47:54 +00:00
q += "?,"
2017-08-06 15:22:18 +00:00
}
2019-08-28 06:47:54 +00:00
q = q [ 0 : len ( q ) - 1 ]
2017-08-06 15:22:18 +00:00
2019-08-28 06:47:54 +00:00
rows , err := qgen . NewAcc ( ) . Select ( "users" ) . Columns ( "uid,name,group,active,is_super_admin,session,email,avatar,message,url_prefix,url_name,level,score,liked,last_ip,temp_group" ) . Where ( "uid IN(" + q + ")" ) . Query ( idList ... )
2017-08-06 15:22:18 +00:00
if err != nil {
2018-02-15 13:15:27 +00:00
return list , err
2017-08-06 15:22:18 +00:00
}
2019-03-11 08:47:45 +00:00
defer rows . Close ( )
2017-08-06 15:22:18 +00:00
for rows . Next ( ) {
2019-07-25 07:00:49 +00:00
u := & User { Loggedin : true }
err := rows . Scan ( & u . ID , & u . Name , & u . Group , & u . Active , & u . IsSuperAdmin , & u . Session , & u . Email , & u . RawAvatar , & u . Message , & u . URLPrefix , & u . URLName , & u . Level , & u . Score , & u . Liked , & u . LastIP , & u . TempGroup )
2017-08-06 15:22:18 +00:00
if err != nil {
2018-02-15 13:15:27 +00:00
return list , err
2017-08-06 15:22:18 +00:00
}
2019-07-25 07:00:49 +00:00
u . Init ( )
s . cache . Set ( u )
list [ u . ID ] = u
2017-08-06 15:22:18 +00:00
}
2019-03-11 08:47:45 +00:00
err = rows . Err ( )
if err != nil {
return list , err
}
2017-08-06 15:22:18 +00:00
// Did we miss any users?
2017-09-03 04:50:31 +00:00
if idCount > len ( list ) {
var sidList string
2017-08-06 15:22:18 +00:00
for _ , id := range ids {
_ , ok := list [ id ]
if ! ok {
2017-09-03 04:50:31 +00:00
sidList += strconv . Itoa ( id ) + ","
2017-08-06 15:22:18 +00:00
}
}
2018-12-06 11:54:20 +00:00
if sidList != "" {
sidList = sidList [ 0 : len ( sidList ) - 1 ]
2019-02-23 06:29:19 +00:00
err = errors . New ( "Unable to find users with the following IDs: " + sidList )
2017-08-06 15:22:18 +00:00
}
}
2017-11-11 23:34:27 +00:00
return list , err
2017-08-06 15:22:18 +00:00
}
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) BypassGet ( id int ) ( * User , error ) {
u := & User { ID : id , Loggedin : true }
err := s . get . QueryRow ( id ) . Scan ( & u . Name , & u . Group , & u . Active , & u . IsSuperAdmin , & u . Session , & u . Email , & u . RawAvatar , & u . Message , & u . URLPrefix , & u . URLName , & u . Level , & u . Score , & u . Liked , & u . LastIP , & u . TempGroup )
u . Init ( )
return u , err
2017-06-13 07:12:58 +00:00
}
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) Reload ( id int ) error {
u := & User { ID : id , Loggedin : true }
err := s . get . QueryRow ( id ) . Scan ( & u . Name , & u . Group , & u . Active , & u . IsSuperAdmin , & u . Session , & u . Email , & u . RawAvatar , & u . Message , & u . URLPrefix , & u . URLName , & u . Level , & u . Score , & u . Liked , & u . LastIP , & u . TempGroup )
2017-06-13 07:12:58 +00:00
if err != nil {
2019-08-28 06:47:54 +00:00
s . cache . Remove ( id )
2017-06-13 07:12:58 +00:00
return err
}
2019-08-28 06:47:54 +00:00
u . Init ( )
_ = s . cache . Set ( u )
2018-11-19 23:06:15 +00:00
TopicListThaw . Thaw ( )
2017-06-13 07:12:58 +00:00
return nil
}
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) Exists ( id int ) bool {
err := s . exists . QueryRow ( id ) . Scan ( & id )
2017-10-16 07:32:58 +00:00
if err != nil && err != ErrNoRows {
LogError ( err )
}
return err != ErrNoRows
2017-09-15 22:20:01 +00:00
}
2017-10-16 07:32:58 +00:00
// TODO: Change active to a bool?
2018-05-28 06:27:12 +00:00
// TODO: Use unique keys for the usernames
2019-08-28 06:47:54 +00:00
func ( s * DefaultUserStore ) Create ( username string , password string , email string , group int , active bool ) ( int , error ) {
2018-03-17 08:16:43 +00:00
// TODO: Strip spaces?
// ? This number might be a little screwy with Unicode, but it's the only consistent thing we have, as Unicode characters can be any number of bytes in theory?
if len ( username ) > Config . MaxUsernameLength {
return 0 , ErrLongUsername
}
2017-06-25 09:56:39 +00:00
// Is this username already taken..?
2019-08-28 06:47:54 +00:00
err := s . usernameExists . QueryRow ( username ) . Scan ( & username )
2017-06-28 12:05:26 +00:00
if err != ErrNoRows {
2017-11-11 04:06:16 +00:00
return 0 , ErrAccountExists
2017-06-25 09:56:39 +00:00
}
2017-11-11 04:06:16 +00:00
salt , err := GenerateSafeString ( SaltLength )
2017-06-25 09:56:39 +00:00
if err != nil {
return 0 , err
}
2017-09-03 04:50:31 +00:00
hashedPassword , err := bcrypt . GenerateFromPassword ( [ ] byte ( password + salt ) , bcrypt . DefaultCost )
2017-06-25 09:56:39 +00:00
if err != nil {
return 0 , err
}
2019-08-28 06:47:54 +00:00
res , err := s . register . Exec ( username , email , string ( hashedPassword ) , salt , group , active )
2017-06-25 09:56:39 +00:00
if err != nil {
return 0 , err
}
2017-09-03 04:50:31 +00:00
lastID , err := res . LastInsertId ( )
return int ( lastID ) , err
2017-06-25 09:56:39 +00:00
}
2019-06-01 12:31:48 +00:00
// Count returns the total number of users registered on the forums
func ( s * DefaultUserStore ) Count ( ) ( count int ) {
err := s . count . QueryRow ( ) . Scan ( & count )
2017-08-15 13:47:56 +00:00
if err != nil {
LogError ( err )
}
2019-06-01 12:31:48 +00:00
return count
2017-08-15 13:47:56 +00:00
}
2019-06-01 12:31:48 +00:00
func ( s * DefaultUserStore ) SetCache ( cache UserCache ) {
s . cache = cache
2017-06-13 07:12:58 +00:00
}
2017-11-23 05:37:08 +00:00
// TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it.
2019-06-01 12:31:48 +00:00
func ( s * DefaultUserStore ) GetCache ( ) UserCache {
_ , ok := s . cache . ( * NullUserCache )
2017-11-23 05:37:08 +00:00
if ok {
return nil
2017-08-15 13:47:56 +00:00
}
2019-06-01 12:31:48 +00:00
return s . cache
2017-10-21 00:27:47 +00:00
}