add user model / service and database maintenance command

This commit is contained in:
MaysWind
2020-10-17 19:46:55 +08:00
parent a7df339f47
commit c9ae001aef
7 changed files with 433 additions and 2 deletions
+37
View File
@@ -0,0 +1,37 @@
package cmd
import (
"github.com/urfave/cli"
"github.com/mayswind/lab/pkg/datastore"
"github.com/mayswind/lab/pkg/log"
"github.com/mayswind/lab/pkg/models"
)
var Database = cli.Command{
Name: "database",
Usage: "lab database maintenance",
Subcommands: []cli.Command{
{
Name: "update",
Usage: "Update database structure",
Action: updateDatabaseStructure,
},
},
}
func updateDatabaseStructure(c *cli.Context) error {
_, err := initializeSystem(c)
if err != nil {
return err
}
log.BootInfof("[database.updateDatabaseStructure] starting maintaining")
_ = datastore.Container.UserStore.SyncStructs(new(models.User))
log.BootInfof("[database.updateDatabaseStructure] maintained successfully")
return nil
}
+71
View File
@@ -0,0 +1,71 @@
package cmd
import (
"encoding/json"
"os"
"github.com/urfave/cli"
"github.com/mayswind/lab/pkg/datastore"
"github.com/mayswind/lab/pkg/log"
"github.com/mayswind/lab/pkg/settings"
"github.com/mayswind/lab/pkg/uuid"
)
func initializeSystem(c *cli.Context) (*settings.Config, error) {
var err error
configFilePath := c.GlobalString("conf-path")
if configFilePath != "" {
if _, err = os.Stat(configFilePath); err != nil {
log.BootErrorf("[initializer.initializeSystem] cannot load configuration from custom config path %s, because file not exists", configFilePath)
return nil, err
}
log.BootInfof("[initializer.initializeSystem] will loading configuration from custom config path %s", configFilePath)
} else {
configFilePath, err = settings.GetDefaultConfigFilePath()
if err != nil {
log.BootErrorf("[initializer.initializeSystem] cannot get default configuration path, because %s", err.Error())
return nil, err
}
log.BootInfof("[initializer.initializeSystem] will load configuration from default config path %s", configFilePath)
}
config, err := settings.LoadConfiguration(configFilePath)
if err != nil {
log.BootErrorf("[initializer.initializeSystem] cannot load configuration, because %s", err.Error())
return nil, err
}
settings.SetCurrentConfig(config)
err = datastore.InitializeDataStore(config)
if err != nil {
log.BootErrorf("[initializer.initializeSystem] initializes data store failed, because %s", err.Error())
return nil, err
}
err = log.SetLoggerConfiguration(config)
if err != nil {
log.BootErrorf("[initializer.initializeSystem] sets logger configuration failed, because %s", err.Error())
return nil, err
}
err = uuid.InitializeUuidGenerator(config)
if err != nil {
log.BootErrorf("[initializer.initializeSystem] initializes uuid generator failed, because %s", err.Error())
return nil, err
}
cfgJson, _ := json.Marshal(config)
log.BootInfof("[initializer.initializeSystem] has loaded configuration %s", cfgJson)
return config, nil
}
+1
View File
@@ -0,0 +1 @@
package lab
+3 -2
View File
@@ -13,6 +13,7 @@ var (
ErrUserPasswordWrong = NewNormalError(NORMAL_SUBCATEGORY_USER, 5, http.StatusBadRequest, "password is wrong") ErrUserPasswordWrong = NewNormalError(NORMAL_SUBCATEGORY_USER, 5, http.StatusBadRequest, "password is wrong")
ErrUsernameAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_USER, 6, http.StatusBadRequest, "username already exists") ErrUsernameAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_USER, 6, http.StatusBadRequest, "username already exists")
ErrUserEmailAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_USER, 7, http.StatusBadRequest, "email already exists") ErrUserEmailAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_USER, 7, http.StatusBadRequest, "email already exists")
ErrLoginNameOrPasswordInvalid = NewNormalError(NORMAL_SUBCATEGORY_USER, 8, http.StatusUnauthorized, "login name or password is invalid") ErrLoginNameInvalid = NewNormalError(NORMAL_SUBCATEGORY_USER, 8, http.StatusUnauthorized, "login name is invalid")
ErrLoginNameOrPasswordWrong = NewNormalError(NORMAL_SUBCATEGORY_USER, 9, http.StatusUnauthorized, "login name or password is wrong") ErrLoginNameOrPasswordInvalid = NewNormalError(NORMAL_SUBCATEGORY_USER, 9, http.StatusUnauthorized, "login name or password is invalid")
ErrLoginNameOrPasswordWrong = NewNormalError(NORMAL_SUBCATEGORY_USER, 10, http.StatusUnauthorized, "login name or password is wrong")
) )
+56
View File
@@ -0,0 +1,56 @@
package models
type UserType byte
const (
USER_TYPE_NORMAL UserType = 0
USER_TYPE_ADMIN UserType = 63
USER_TYPE_SUPER_ADMIN UserType = 127
)
type User struct {
Uid int64 `xorm:"PK"`
Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"`
Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"`
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
Password string `xorm:"VARCHAR(64) NOT NULL"`
Salt string `xorm:"VARCHAR(10) NOT NULL"`
Rands string `xorm:"VARCHAR(10) NOT NULL"`
Type UserType `xorm:"TINYINT NOT NULL"`
IsAdmin bool `xorm:"NOT NULL"`
Deleted bool `xorm:"NOT NULL"`
EmailVerified bool `xorm:"NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
LastLoginUnixTime int64
}
type UserLoginRequest struct {
LoginName string `json:"loginName" binding:"required,notBlank,max=100,validUsername|validEmail"`
Password string `json:"password" binding:"required,min=6,max=128"`
}
type UserRegisterRequest struct {
Username string `json:"username" binding:"required,notBlank,max=32,validUsername"`
Email string `json:"email" binding:"required,notBlank,max=100,validEmail"`
Nickname string `json:"nickname" binding:"required,notBlank,max=64"`
Password string `json:"password" binding:"required,min=6,max=128"`
}
type UserProfileUpdateRequest struct {
Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"`
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
Password string `json:"password" binding:"omitempty,min=6,max=128"`
}
type UserProfileResponse struct {
Uid string `json:"uid"`
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
Type UserType `json:"type"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
LastLoginAt int64 `json:"lastLoginAt"`
}
+39
View File
@@ -0,0 +1,39 @@
package services
import (
"github.com/mayswind/lab/pkg/datastore"
"github.com/mayswind/lab/pkg/settings"
"github.com/mayswind/lab/pkg/uuid"
)
type ServiceUsingDB struct {
container *datastore.DataStoreContainer
}
func (s *ServiceUsingDB) UserDB() *datastore.Database {
return s.container.UserStore.Choose(0)
}
func (s *ServiceUsingDB) TokenDB(uid int64) *datastore.Database {
return s.container.TokenStore.Choose(uid)
}
func (s *ServiceUsingDB) UserDataDB(uid int64) *datastore.Database {
return s.container.UserDataStore.Choose(uid)
}
type ServiceUsingConfig struct {
container *settings.ConfigContainer
}
func (s *ServiceUsingConfig) CurrentConfig() *settings.Config {
return s.container.Current
}
type ServiceUsingUuid struct {
container *uuid.UuidContainer
}
func (s *ServiceUsingUuid) GenerateUuid(uuidType uuid.UuidType) int64 {
return s.container.GenerateUuid(uuidType)
}
+226
View File
@@ -0,0 +1,226 @@
package services
import (
"time"
"xorm.io/xorm"
"github.com/mayswind/lab/pkg/datastore"
"github.com/mayswind/lab/pkg/errs"
"github.com/mayswind/lab/pkg/models"
"github.com/mayswind/lab/pkg/utils"
"github.com/mayswind/lab/pkg/uuid"
)
type UserService struct {
ServiceUsingDB
ServiceUsingUuid
}
var (
Users = &UserService{
ServiceUsingDB: ServiceUsingDB{
container: datastore.Container,
},
ServiceUsingUuid: ServiceUsingUuid{
container: uuid.Container,
},
}
)
func (s *UserService) GetUserByUsernameOrEmailAndPassword(loginname string, password string) (*models.User, error) {
var user *models.User
var err error
if utils.IsValidUsername(loginname) {
user, err = s.GetUserByUsername(loginname)
} else if utils.IsValidEmail(loginname) {
user, err = s.GetUserByEmail(loginname)
} else {
err = errs.ErrLoginNameInvalid
}
if err != nil {
return nil, err
}
if !s.IsPasswordEqualsUserPassword(password, user) {
return nil, errs.ErrUserPasswordWrong
}
return user, nil
}
func (s *UserService) GetUserById(uid int64) (*models.User, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
user := &models.User{}
has, err := s.UserDB().ID(uid).Where("deleted=?", false).Get(user)
if err != nil {
return nil, err
} else if !has {
return nil, errs.ErrUserNotFound
}
return user, nil
}
func (s *UserService) GetUserByUsername(username string) (*models.User, error) {
if username == "" {
return nil, errs.ErrUsernameIsEmpty
}
user := &models.User{}
has, err := s.UserDB().Where("username=? AND deleted=?", username, false).Get(user)
if err != nil {
return nil, err
} else if !has {
return nil, errs.ErrUserNotFound
}
return user, nil
}
func (s *UserService) GetUserByEmail(email string) (*models.User, error) {
if email == "" {
return nil, errs.ErrEmailIsEmpty
}
user := &models.User{}
has, err := s.UserDB().Where("email=? AND deleted=?", email, false).Get(user)
if err != nil {
return nil, err
} else if !has {
return nil, errs.ErrUserNotFound
}
return user, nil
}
func (s *UserService) CreateUser(user *models.User) error {
exists, err := s.ExistsUsername(user.Username)
if err != nil {
return err
} else if exists {
return errs.ErrUsernameAlreadyExists
}
exists, err = s.ExistsEmail(user.Email)
if err != nil {
return err
} else if exists {
return errs.ErrUserEmailAlreadyExists
}
if user.Password == "" {
return errs.ErrPasswordIsEmpty
}
if user.Salt, err = utils.GetRandomString(10); err != nil {
return err
}
if user.Rands, err = utils.GetRandomString(10); err != nil {
return err
}
user.Uid = s.GenerateUuid(uuid.UUID_TYPE_USER)
user.Password = utils.EncodePassword(user.Password, user.Salt)
user.Deleted = false
user.CreatedUnixTime = time.Now().Unix()
user.UpdatedUnixTime = time.Now().Unix()
user.LastLoginUnixTime = time.Now().Unix()
return s.UserDB().DoTranscation(func(sess *xorm.Session) error {
_, err := sess.Insert(user)
return err
})
}
func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err error) {
if user.Uid <= 0 {
return false, errs.ErrUserIdInvalid
}
var updateCols []string
now := time.Now().Unix()
keyProfileUpdated = false
if user.Email != "" {
user.EmailVerified = false
updateCols = append(updateCols, "email")
updateCols = append(updateCols, "email_verified")
}
if user.Password != "" {
user.Password = utils.EncodePassword(user.Password, user.Salt)
keyProfileUpdated = true
updateCols = append(updateCols, "password")
}
if user.Nickname != "" {
updateCols = append(updateCols, "nickname")
}
user.UpdatedUnixTime = now
updateCols = append(updateCols, "updated_unix_time")
err = s.UserDB().DoTranscation(func(sess *xorm.Session) error {
updatedRows, err := sess.ID(user.Uid).Where("deleted=?", false).Cols(updateCols...).Update(user)
if updatedRows < 1 {
return errs.ErrUserNotFound
}
return err
})
if err != nil {
return false, err
}
return keyProfileUpdated, nil
}
func (s *UserService) UpdateUserLastLoginTime(uid int64) error {
if uid <= 0 {
return errs.ErrUserIdInvalid
}
return s.UserDB().DoTranscation(func(sess *xorm.Session) error {
_, err := sess.ID(uid).Where("deleted=?", false).Cols("last_login_unix_time").Update(&models.User{LastLoginUnixTime: time.Now().Unix()})
return err
})
}
func (s *UserService) ExistsUsername(username string) (bool, error) {
if username == "" {
return false, errs.ErrUsernameIsEmpty
}
return s.UserDB().Cols("username").Where("username=? AND deleted=?", username, false).Exist(&models.User{})
}
func (s *UserService) ExistsEmail(email string) (bool, error) {
if email == "" {
return false, errs.ErrEmailIsEmpty
}
return s.UserDB().Cols("email").Where("email=? AND deleted=?", email, false).Exist(&models.User{})
}
func (s *UserService) IsPasswordEqualsUserPassword(password string, user *models.User) bool {
return user.Password == utils.EncodePassword(password, user.Salt)
}