diff --git a/action.go b/action.go new file mode 100644 index 0000000..b94c5b2 --- /dev/null +++ b/action.go @@ -0,0 +1,49 @@ +package usermngmt + +import ( + "github.com/Selly-Modules/usermngmt/model" + "github.com/Selly-Modules/usermngmt/role" + "github.com/Selly-Modules/usermngmt/user" +) + +// +// User +// + +// user methods + +// CreateUser ... +func (s Service) CreateUser(payload model.UserCreateOptions) error { + return user.Create(payload) +} + +// UpdateUser ... +func (s Service) UpdateUser(userID string, payload model.UserUpdateOptions) error { + return user.UpdateByUserID(userID, payload) +} + +// ChangeUserPassword ... +func (s Service) ChangeUserPassword(userID string, payload model.ChangePasswordOptions) error { + return user.ChangeUserPassword(userID, payload) +} + +// ChangeUserStatus ... +func (s Service) ChangeUserStatus(userID, newStatus string) error { + return user.ChangeUserStatus(userID, newStatus) +} + +// GetAllUser ... +func (s Service) GetAllUser(query model.UserAllQuery) model.UserAll { + return user.All(query) +} + +// +// Role +// + +// role methods + +// CreateRole ... +func (s Service) CreateRole(payload model.RoleCreateOptions) error { + return role.Create(payload) +} diff --git a/action_create.go b/action_create.go deleted file mode 100644 index 616eae2..0000000 --- a/action_create.go +++ /dev/null @@ -1,61 +0,0 @@ -package usermngmt - -import ( - "context" - - "github.com/Selly-Modules/mongodb" -) - -// CreateOptions ... -type CreateOptions struct { - Name string - Phone string - Email string - Password string - Status string - RoleID string - Other string -} - -// Create ... -func (s Service) Create(payload CreateOptions) error { - var ( - ctx = context.Background() - ) - - // Validate payload - if err := payload.validate(ctx); err != nil { - return err - } - - // New user data from payload - doc, err := payload.newUser() - if err != nil { - return err - } - - // Create user - if err = s.userCreate(ctx, doc); err != nil { - return err - } - - return nil -} - -func (payload CreateOptions) newUser() (result User, err error) { - timeNow := now() - roleID, _ := mongodb.NewIDFromString(payload.RoleID) - return User{ - ID: mongodb.NewObjectID(), - Name: payload.Name, - SearchString: getSearchString(payload.Name, payload.Phone, payload.Email), - Phone: payload.Phone, - Email: payload.Email, - HashedPassword: hashPassword(payload.Password), - Status: payload.Status, - RoleID: roleID, - Other: payload.Other, - CreatedAt: timeNow, - UpdatedAt: timeNow, - }, nil -} diff --git a/action_get_all.go b/action_get_all.go deleted file mode 100644 index 47b65c7..0000000 --- a/action_get_all.go +++ /dev/null @@ -1,82 +0,0 @@ -package usermngmt - -import ( - "context" - "sync" - - "go.mongodb.org/mongo-driver/bson" -) - -// AllQuery ... -type AllQuery struct { - Page int64 - Limit int64 - Keyword string - RoleID string - Status string -} - -// All ... -func (s Service) All(queryParams AllQuery) (r ResponseUserAll) { - var ( - ctx = context.Background() - wg sync.WaitGroup - cond = bson.M{} - ) - query := commonQuery{ - Page: queryParams.Page, - Limit: queryParams.Limit, - Keyword: queryParams.Keyword, - RoleID: queryParams.RoleID, - Status: queryParams.Status, - Sort: bson.M{"createdAt": -1}, - } - - // Assign condition - query.SetDefaultLimit() - query.AssignKeyword(cond) - query.AssignRoleID(cond) - query.AssignStatus(cond) - - wg.Add(1) - go func() { - defer wg.Done() - docs := s.userFindByCondition(ctx, cond, query.GetFindOptionsUsingPage()) - r.List = getResponseList(ctx, docs) - }() - - wg.Add(1) - go func() { - defer wg.Done() - r.Total = s.userCountByCondition(ctx, cond) - }() - - wg.Wait() - - return -} - -func getResponseList(ctx context.Context, users []User) []ResponseUser { - res := make([]ResponseUser, 0) - - for _, user := range users { - role, _ := s.roleFindByID(ctx, user.RoleID) - res = append(res, ResponseUser{ - ID: user.ID.Hex(), - Name: user.Name, - Phone: user.Phone, - Email: user.Email, - Status: user.Status, - Role: RoleShort{ - ID: role.ID.Hex(), - Name: role.Name, - IsAdmin: role.IsAdmin, - }, - Other: user.Other, - CreatedAt: user.CreatedAt, - UpdatedAt: user.UpdatedAt, - }) - } - - return res -} diff --git a/action_update.go b/action_update.go deleted file mode 100644 index 6612e13..0000000 --- a/action_update.go +++ /dev/null @@ -1,125 +0,0 @@ -package usermngmt - -import ( - "context" - "errors" - - "github.com/Selly-Modules/mongodb" - "go.mongodb.org/mongo-driver/bson" -) - -// UpdateOptions ... -type UpdateOptions struct { - Name string - Phone string - Email string - RoleID string - Other string -} - -// ChangePasswordOptions ... -type ChangePasswordOptions struct { - OldPassword string - NewPassword string -} - -// UpdateByUserID ... -func (s Service) UpdateByUserID(userID string, payload UpdateOptions) error { - var ( - ctx = context.Background() - ) - - // Validate payload - if err := payload.validate(ctx); err != nil { - return err - } - - // Setup condition - id, _ := mongodb.NewIDFromString(userID) - cond := bson.M{ - "_id": id, - } - - // Setup update data - roleID, _ := mongodb.NewIDFromString(payload.RoleID) - updateData := bson.M{ - "$set": bson.M{ - "name": payload.Name, - "searchString": getSearchString(payload.Name, payload.Phone, payload.Email), - "phone": payload.Phone, - "email": payload.Email, - "roleId": roleID, - "other": payload.Other, - "updatedAt": now(), - }, - } - - // Update - if err := s.userUpdateOneByCondition(ctx, cond, updateData); err != nil { - return err - } - - return nil -} - -// ChangeUserPassword ... -func (s Service) ChangeUserPassword(userID string, opt ChangePasswordOptions) error { - var ( - ctx = context.Background() - ) - - // Validate payload - err := opt.validate(userID) - if err != nil { - return err - } - - // Find user - id, _ := mongodb.NewIDFromString(userID) - user, _ := s.userFindByID(ctx, id) - if user.ID.IsZero() { - return errors.New("user not found") - } - - // Check old password - if isValid := checkPasswordHash(opt.OldPassword, user.HashedPassword); !isValid { - return errors.New("the password is incorrect") - } - - // Update password - if err = s.userUpdateOneByCondition(ctx, bson.M{"_id": user.ID}, bson.M{ - "$set": bson.M{ - "hashedPassword": hashPassword(opt.NewPassword), - "updatedAt": now(), - }, - }); err != nil { - return err - } - - return nil -} - -// ChangeUserStatus ... -func (s Service) ChangeUserStatus(userID, newStatus string) error { - var ( - ctx = context.Background() - ) - - // Validate userID - id, isValid := mongodb.NewIDFromString(userID) - if !isValid { - return errors.New("invalid user id data") - } - - // Update status - if err := s.userUpdateOneByCondition(ctx, bson.M{"_id": id}, bson.M{ - "$set": bson.M{ - "status": newStatus, - "updatedAt": now(), - }, - }); err != nil { - return err - } - - return nil -} diff --git a/constant.go b/constant.go deleted file mode 100644 index ab8a97d..0000000 --- a/constant.go +++ /dev/null @@ -1,12 +0,0 @@ -package usermngmt - -// Constant ... -const ( - tableUser = "users" - tableRole = "roles" - tablePrefixDefault = "usermngmt" - - timezoneHCM = "Asia/Ho_Chi_Minh" - - passwordHashingCost = 14 -) diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..62ba40d --- /dev/null +++ b/database/db.go @@ -0,0 +1,34 @@ +package database + +import ( + "fmt" + + "go.mongodb.org/mongo-driver/mongo" +) + +// Table +var ( + tableUser = "users" + tableRole = "roles" +) + +var ( + db *mongo.Database + prefix string +) + +// Set ... +func Set(instance *mongo.Database, tablePrefix string) { + db = instance + prefix = tablePrefix +} + +// GetUserCol ... +func GetUserCol() *mongo.Collection { + return db.Collection(fmt.Sprintf("%s-%s", prefix, tableUser)) +} + +// GetRoleCol ... +func GetRoleCol() *mongo.Collection { + return db.Collection(fmt.Sprintf("%s-%s", prefix, tableRole)) +} diff --git a/go.mod b/go.mod index 8091cd4..70aa30c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/selly-Modules/usermngmt +module github.com/Selly-Modules/usermngmt go 1.17 diff --git a/internal/constant.go b/internal/constant.go new file mode 100644 index 0000000..c048f83 --- /dev/null +++ b/internal/constant.go @@ -0,0 +1,10 @@ +package internal + +// Constant ... +const ( + timezoneHCM = "Asia/Ho_Chi_Minh" + + passwordHashingCost = 14 + + TablePrefixDefault = "usermngmt" +) diff --git a/helper.go b/internal/helper.go similarity index 70% rename from helper.go rename to internal/helper.go index fbee0db..597fef5 100644 --- a/helper.go +++ b/internal/helper.go @@ -1,4 +1,4 @@ -package usermngmt +package internal import ( "fmt" @@ -7,17 +7,20 @@ import ( "golang.org/x/crypto/bcrypt" ) -func hashPassword(password string) string { +// HashPassword ... +func HashPassword(password string) string { bytes, _ := bcrypt.GenerateFromPassword([]byte(password), passwordHashingCost) return string(bytes) } -func checkPasswordHash(password, hash string) bool { +// CheckPasswordHash ... +func CheckPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } -func getSearchString(fieldList ...string) string { +// GetSearchString ... +func GetSearchString(fieldList ...string) string { var ( searchList = make([]interface{}, 0) format = "" diff --git a/time.go b/internal/time.go similarity index 88% rename from time.go rename to internal/time.go index 8e31061..f3acf96 100644 --- a/time.go +++ b/internal/time.go @@ -1,4 +1,4 @@ -package usermngmt +package internal import "time" @@ -14,7 +14,7 @@ func getHCMLocation() *time.Location { return l } -// now ... -func now() time.Time { +// Now ... +func Now() time.Time { return time.Now().In(getHCMLocation()) } diff --git a/model.go b/model.go deleted file mode 100644 index a44ee3d..0000000 --- a/model.go +++ /dev/null @@ -1,59 +0,0 @@ -package usermngmt - -import ( - "time" - - "go.mongodb.org/mongo-driver/bson/primitive" -) - -// User ... -type User struct { - ID primitive.ObjectID `bson:"_id" json:"_id"` - Name string `bson:"name" json:"name"` - SearchString string `bson:"searchString" json:"-"` - Phone string `bson:"phone" json:"phone"` // unique - Email string `bson:"email" json:"email"` // unique - HashedPassword string `bson:"hashedPassword" json:"-"` - Status string `bson:"status" json:"status"` - RoleID primitive.ObjectID `bson:"roleId" json:"roleId"` - Other string `bson:"other" json:"other"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` - UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` -} - -// ResponseUser ... -type ResponseUser struct { - ID string `json:"_id"` - Name string `json:"name"` - Phone string `json:"phone"` - Email string `json:"email"` - Status string `json:"status"` - Role RoleShort `json:"role"` - Other string `json:"other"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -// Role ... -type Role struct { - ID primitive.ObjectID `bson:"_id" json:"_id"` - Name string `bson:"name" json:"name"` - Code string `bson:"code" json:"code"` - IsAdmin bool `bson:"isAdmin" json:"isAdmin"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` - UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` -} - -type RoleShort struct { - ID string `json:"_id"` - Name string `json:"name"` - IsAdmin bool `json:"isAdmin"` -} - -type ( - // ResponseUserAll ... - ResponseUserAll struct { - List []ResponseUser `json:"list"` - Total int64 `json:"total"` - } -) diff --git a/model/db.go b/model/db.go new file mode 100644 index 0000000..53aca13 --- /dev/null +++ b/model/db.go @@ -0,0 +1,32 @@ +package model + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// DBRole ... +type DBRole struct { + ID primitive.ObjectID `bson:"_id" json:"_id"` + Name string `bson:"name" json:"name"` + Code string `bson:"code" json:"code"` + IsAdmin bool `bson:"isAdmin" json:"isAdmin"` + CreatedAt time.Time `bson:"createdAt" json:"createdAt"` + UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` +} + +// DBUser ... +type DBUser struct { + ID primitive.ObjectID `bson:"_id"` + Name string `bson:"name"` + SearchString string `bson:"searchString"` + Phone string `bson:"phone"` // unique + Email string `bson:"email"` // unique + HashedPassword string `bson:"hashedPassword"` + Status string `bson:"status"` + RoleID primitive.ObjectID `bson:"roleId"` + Other string `bson:"other"` + CreatedAt time.Time `bson:"createdAt"` + UpdatedAt time.Time `bson:"updatedAt"` +} diff --git a/query.go b/model/query.go similarity index 72% rename from query.go rename to model/query.go index 704edfa..b2af79d 100644 --- a/query.go +++ b/model/query.go @@ -1,4 +1,4 @@ -package usermngmt +package model import ( "github.com/Selly-Modules/mongodb" @@ -6,7 +6,8 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) -type commonQuery struct { +// CommonQuery ... +type CommonQuery struct { Page int64 Limit int64 Keyword string @@ -16,14 +17,14 @@ type commonQuery struct { } // AssignKeyword ... -func (q *commonQuery) AssignKeyword(cond bson.M) { +func (q *CommonQuery) AssignKeyword(cond bson.M) { if q.Keyword != "" { cond["searchString"] = mongodb.GenerateQuerySearchString(q.Keyword) } } // AssignRoleID ... -func (q *commonQuery) AssignRoleID(cond bson.M) { +func (q *CommonQuery) AssignRoleID(cond bson.M) { if q.RoleID != "" { if id, isValid := mongodb.NewIDFromString(q.RoleID); isValid { cond["roleId"] = id @@ -32,14 +33,14 @@ func (q *commonQuery) AssignRoleID(cond bson.M) { } // AssignStatus ... -func (q *commonQuery) AssignStatus(cond bson.M) { +func (q *CommonQuery) AssignStatus(cond bson.M) { if q.Status != "" { cond["status"] = q.Status } } // GetFindOptionsUsingPage ... -func (q *commonQuery) GetFindOptionsUsingPage() *options.FindOptions { +func (q *CommonQuery) GetFindOptionsUsingPage() *options.FindOptions { opts := options.Find() if q.Limit > 0 { opts.SetLimit(q.Limit).SetSkip(q.Limit * q.Page) @@ -51,7 +52,7 @@ func (q *commonQuery) GetFindOptionsUsingPage() *options.FindOptions { } // SetDefaultLimit ... -func (q *commonQuery) SetDefaultLimit() { +func (q *CommonQuery) SetDefaultLimit() { if q.Limit <= 0 || q.Limit > 20 { q.Limit = 20 } diff --git a/model/role_request.go b/model/role_request.go new file mode 100644 index 0000000..81a8e0f --- /dev/null +++ b/model/role_request.go @@ -0,0 +1,6 @@ +package model + +// RoleCreateOptions ... +type RoleCreateOptions struct { + Name string +} diff --git a/model/role_response.go b/model/role_response.go new file mode 100644 index 0000000..abe20f0 --- /dev/null +++ b/model/role_response.go @@ -0,0 +1,8 @@ +package model + +// RoleShort ... +type RoleShort struct { + ID string `json:"_id"` + Name string `json:"name"` + IsAdmin bool `json:"isAdmin"` +} diff --git a/validate.go b/model/user_request.go similarity index 56% rename from validate.go rename to model/user_request.go index a856ab3..d27b3f1 100644 --- a/validate.go +++ b/model/user_request.go @@ -1,14 +1,48 @@ -package usermngmt +package model import ( - "context" "errors" "github.com/Selly-Modules/logger" - "github.com/Selly-Modules/mongodb" ) -func (co CreateOptions) validate(ctx context.Context) error { +// UserCreateOptions ... +type UserCreateOptions struct { + Name string + Phone string + Email string + Password string + Status string + RoleID string + Other string +} + +// UserUpdateOptions ... +type UserUpdateOptions struct { + Name string + Phone string + Email string + RoleID string + Other string +} + +// ChangePasswordOptions ... +type ChangePasswordOptions struct { + OldPassword string + NewPassword string +} + +// UserAllQuery ... +type UserAllQuery struct { + Page int64 + Limit int64 + Keyword string + RoleID string + Status string +} + +// Validate ... +func (co UserCreateOptions) Validate() error { // Name if co.Name == "" { logger.Error("usermngmt - Create: no Name data", logger.LogData{ @@ -57,74 +91,48 @@ func (co CreateOptions) validate(ctx context.Context) error { return errors.New("no role id data") } - // Find roleID exists or not - roleID, isValid := mongodb.NewIDFromString(co.RoleID) - if !isValid { - return errors.New("invalid role id data") - } - if !s.isRoleIDExisted(ctx, roleID) { - return errors.New("role id does not exist") - } - - // Find phone number,email exists or not - if s.isPhoneNumberOrEmailExisted(ctx, co.Phone, co.Email) { - return errors.New("phone number or email already existed") - } - return nil } -func (co UpdateOptions) validate(ctx context.Context) error { +// Validate ... +func (uo UserUpdateOptions) Validate() error { // Name - if co.Name == "" { + if uo.Name == "" { logger.Error("usermngmt - Update: no name data", logger.LogData{ - "payload": co, + "payload": uo, }) return errors.New("no name data") } // Phone - if co.Phone == "" { + if uo.Phone == "" { logger.Error("usermngmt - Update: no phone data", logger.LogData{ - "payload": co, + "payload": uo, }) return errors.New("no phone data") } // Email - if co.Email == "" { + if uo.Email == "" { logger.Error("usermngmt - Update: no email data", logger.LogData{ - "payload": co, + "payload": uo, }) return errors.New("no email data") } // RoleID - if co.RoleID == "" { + if uo.RoleID == "" { logger.Error("usermngmt - Update: no roleID data", logger.LogData{ - "payload": co, + "payload": uo, }) return errors.New("no role id data") } - // Find roleID exists or not - roleID, isValid := mongodb.NewIDFromString(co.RoleID) - if !isValid { - return errors.New("invalid role id data") - } - if !s.isRoleIDExisted(ctx, roleID) { - return errors.New("role id does not exist") - } - - // Find phone number,email exists or not - if s.isPhoneNumberOrEmailExisted(ctx, co.Phone, co.Email) { - return errors.New("phone number or email already existed") - } - return nil } -func (co ChangePasswordOptions) validate(userID string) error { +// Validate ... +func (co ChangePasswordOptions) Validate() error { // OldPassword, NewPassword if co.OldPassword == "" || co.NewPassword == "" { logger.Error("usermngmt - ChangePassword: old or new password cannot be empty", logger.LogData{ @@ -133,13 +141,5 @@ func (co ChangePasswordOptions) validate(userID string) error { return errors.New("old or new password cannot be empty") } - // UserID - if _, isValid := mongodb.NewIDFromString(userID); !isValid { - logger.Error("usermngmt - ChangePassword: invalid userID data", logger.LogData{ - "payload": co, - }) - return errors.New("invalid user id data") - } - return nil } diff --git a/model/user_response.go b/model/user_response.go new file mode 100644 index 0000000..5344b7b --- /dev/null +++ b/model/user_response.go @@ -0,0 +1,26 @@ +package model + +import ( + "time" +) + +// User ... +type User struct { + ID string `json:"_id"` + Name string `json:"name"` + Phone string `json:"phone"` + Email string `json:"email"` + Status string `json:"status"` + Role RoleShort `json:"role"` + Other string `json:"other"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ( + // UserAll ... + UserAll struct { + List []User `json:"list"` + Total int64 `json:"total"` + } +) diff --git a/role/db.go b/role/db.go new file mode 100644 index 0000000..6b41add --- /dev/null +++ b/role/db.go @@ -0,0 +1,19 @@ +package role + +import ( + "context" + + "github.com/Selly-Modules/usermngmt/database" + "github.com/Selly-Modules/usermngmt/model" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func findByID(ctx context.Context, id primitive.ObjectID) (model.DBRole, error) { + var ( + doc model.DBRole + col = database.GetRoleCol() + ) + err := col.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) + return doc, err +} diff --git a/role/handle.go b/role/handle.go new file mode 100644 index 0000000..9572478 --- /dev/null +++ b/role/handle.go @@ -0,0 +1,20 @@ +package role + +import ( + "context" + + "github.com/Selly-Modules/usermngmt/model" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// FindByID ... +func FindByID(ctx context.Context, id primitive.ObjectID) (model.DBRole, error) { + role, err := findByID(ctx, id) + return role, err +} + +// Create ... +func Create(payload model.RoleCreateOptions) error { + // TODO later + return nil +} diff --git a/db.go b/user/db.go similarity index 59% rename from db.go rename to user/db.go index dbfade4..7134c53 100644 --- a/db.go +++ b/user/db.go @@ -1,31 +1,21 @@ -package usermngmt +package user import ( "context" "fmt" "github.com/Selly-Modules/logger" + "github.com/Selly-Modules/usermngmt/database" + "github.com/Selly-Modules/usermngmt/model" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) -// getUserCollection ... -func (s Service) getUserCollection() *mongo.Collection { - return s.DB.Collection(fmt.Sprintf("%s-%s", s.TablePrefix, tableUser)) -} - -// getRoleCollection ... -func (s Service) getRoleCollection() *mongo.Collection { - return s.DB.Collection(fmt.Sprintf("%s-%s", s.TablePrefix, tableRole)) -} - -func (s Service) isPhoneNumberOrEmailExisted(ctx context.Context, phone, email string) bool { +func isPhoneNumberOrEmailExisted(ctx context.Context, phone, email string) bool { var ( - col = s.getUserCollection() + col = database.GetUserCol() ) - // Find cond := bson.M{ "$or": []bson.M{ @@ -48,11 +38,10 @@ func (s Service) isPhoneNumberOrEmailExisted(ctx context.Context, phone, email s return total != 0 } -func (s Service) isRoleIDExisted(ctx context.Context, roleID primitive.ObjectID) bool { +func isRoleIDExisted(ctx context.Context, roleID primitive.ObjectID) bool { var ( - col = s.getRoleCollection() + col = database.GetRoleCol() ) - // Find cond := bson.M{ "_id": roleID, @@ -68,11 +57,19 @@ func (s Service) isRoleIDExisted(ctx context.Context, roleID primitive.ObjectID) return total != 0 } -func (s Service) userCreate(ctx context.Context, doc User) error { +func roleFindByID(ctx context.Context, id primitive.ObjectID) (model.DBRole, error) { var ( - col = s.getUserCollection() + doc model.DBRole + col = database.GetRoleCol() ) + err := col.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) + return doc, err +} +func create(ctx context.Context, doc model.DBUser) error { + var ( + col = database.GetUserCol() + ) _, err := col.InsertOne(ctx, doc) if err != nil { logger.Error("usermngmt - Create", logger.LogData{ @@ -85,11 +82,10 @@ func (s Service) userCreate(ctx context.Context, doc User) error { return nil } -func (s Service) userUpdateOneByCondition(ctx context.Context, cond interface{}, payload interface{}) error { +func updateOneByCondition(ctx context.Context, cond interface{}, payload interface{}) error { var ( - col = s.getUserCollection() + col = database.GetUserCol() ) - _, err := col.UpdateOne(ctx, cond, payload) if err != nil { logger.Error("usermngmt - Update", logger.LogData{ @@ -103,20 +99,20 @@ func (s Service) userUpdateOneByCondition(ctx context.Context, cond interface{}, return err } -func (s Service) userFindByID(ctx context.Context, id primitive.ObjectID) (User, error) { +func findByID(ctx context.Context, id primitive.ObjectID) (model.DBUser, error) { var ( - col = s.getUserCollection() - doc User + doc model.DBUser + col = database.GetUserCol() ) err := col.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) return doc, err } -func (s Service) userFindByCondition(ctx context.Context, cond interface{}, opts ...*options.FindOptions) (docs []User) { +func findByCondition(ctx context.Context, cond interface{}, opts ...*options.FindOptions) (docs []model.DBUser) { var ( - col = s.getUserCollection() + col = database.GetUserCol() ) - docs = make([]User, 0) + docs = make([]model.DBUser, 0) cursor, err := col.Find(ctx, cond, opts...) if err != nil { @@ -139,12 +135,11 @@ func (s Service) userFindByCondition(ctx context.Context, cond interface{}, opts return } -// userCountByCondition ... -func (s Service) userCountByCondition(ctx context.Context, cond interface{}) int64 { +// countByCondition ... +func countByCondition(ctx context.Context, cond interface{}) int64 { var ( - col = s.getUserCollection() + col = database.GetUserCol() ) - total, err := col.CountDocuments(ctx, cond) if err != nil { logger.Error("usermngmt - Count", logger.LogData{ @@ -154,12 +149,3 @@ func (s Service) userCountByCondition(ctx context.Context, cond interface{}) int } return total } - -func (s Service) roleFindByID(ctx context.Context, id primitive.ObjectID) (Role, error) { - var ( - col = s.getRoleCollection() - doc Role - ) - err := col.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) - return doc, err -} \ No newline at end of file diff --git a/user/handle.go b/user/handle.go new file mode 100644 index 0000000..684013a --- /dev/null +++ b/user/handle.go @@ -0,0 +1,259 @@ +package user + +import ( + "context" + "errors" + "sync" + + "github.com/Selly-Modules/logger" + "github.com/Selly-Modules/mongodb" + "github.com/Selly-Modules/usermngmt/internal" + "github.com/Selly-Modules/usermngmt/model" + "go.mongodb.org/mongo-driver/bson" +) + +// Create ... +func Create(payload model.UserCreateOptions) error { + var ( + ctx = context.Background() + ) + + // Validate payload + if err := payload.Validate(); err != nil { + return err + } + + // Find roleID exists or not + roleID, isValid := mongodb.NewIDFromString(payload.RoleID) + if !isValid { + return errors.New("invalid role id data") + } + if !isRoleIDExisted(ctx, roleID) { + return errors.New("role id does not exist") + } + + // Find phone number,email exists or not + if isPhoneNumberOrEmailExisted(ctx, payload.Phone, payload.Email) { + return errors.New("phone number or email already existed") + } + + // New user data from payload + doc, err := newUser(payload) + if err != nil { + return err + } + + // Create user + if err = create(ctx, doc); err != nil { + return err + } + + return nil +} + +// newUser ... +func newUser(payload model.UserCreateOptions) (result model.DBUser, err error) { + timeNow := internal.Now() + roleID, _ := mongodb.NewIDFromString(payload.RoleID) + return model.DBUser{ + ID: mongodb.NewObjectID(), + Name: payload.Name, + SearchString: internal.GetSearchString(payload.Name, payload.Phone, payload.Email), + Phone: payload.Phone, + Email: payload.Email, + HashedPassword: internal.HashPassword(payload.Password), + Status: payload.Status, + RoleID: roleID, + Other: payload.Other, + CreatedAt: timeNow, + UpdatedAt: timeNow, + }, nil +} + +// All ... +func All(queryParams model.UserAllQuery) (r model.UserAll) { + var ( + ctx = context.Background() + wg sync.WaitGroup + cond = bson.M{} + ) + query := model.CommonQuery{ + Page: queryParams.Page, + Limit: queryParams.Limit, + Keyword: queryParams.Keyword, + RoleID: queryParams.RoleID, + Status: queryParams.Status, + Sort: bson.M{"createdAt": -1}, + } + + // Assign condition + query.SetDefaultLimit() + query.AssignKeyword(cond) + query.AssignRoleID(cond) + query.AssignStatus(cond) + + wg.Add(1) + go func() { + defer wg.Done() + docs := findByCondition(ctx, cond, query.GetFindOptionsUsingPage()) + r.List = getResponseList(ctx, docs) + }() + + wg.Add(1) + go func() { + defer wg.Done() + r.Total = countByCondition(ctx, cond) + }() + + wg.Wait() + + return +} + +func getResponseList(ctx context.Context, users []model.DBUser) []model.User { + res := make([]model.User, 0) + + for _, user := range users { + roleRaw, _ := roleFindByID(ctx, user.RoleID) + res = append(res, model.User{ + ID: user.ID.Hex(), + Name: user.Name, + Phone: user.Phone, + Email: user.Email, + Status: user.Status, + Role: model.RoleShort{ + ID: roleRaw.ID.Hex(), + Name: roleRaw.Name, + IsAdmin: roleRaw.IsAdmin, + }, + Other: user.Other, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }) + } + + return res +} + +// UpdateByUserID ... +func UpdateByUserID(userID string, payload model.UserUpdateOptions) error { + var ( + ctx = context.Background() + ) + + // Validate payload + if err := payload.Validate(); err != nil { + return err + } + + // Find roleID exists or not + roleID, isValid := mongodb.NewIDFromString(payload.RoleID) + if !isValid { + return errors.New("invalid role id data") + } + if !isRoleIDExisted(ctx, roleID) { + return errors.New("role id does not exist") + } + + // Find phone number,email exists or not + if isPhoneNumberOrEmailExisted(ctx, payload.Phone, payload.Email) { + return errors.New("phone number or email already existed") + } + + // Setup condition + id, _ := mongodb.NewIDFromString(userID) + cond := bson.M{ + "_id": id, + } + + // Setup update data + updateData := bson.M{ + "$set": bson.M{ + "name": payload.Name, + "searchString": internal.GetSearchString(payload.Name, payload.Phone, payload.Email), + "phone": payload.Phone, + "email": payload.Email, + "roleId": roleID, + "other": payload.Other, + "updatedAt": internal.Now(), + }, + } + + // Update + if err := updateOneByCondition(ctx, cond, updateData); err != nil { + return err + } + + return nil +} + +// ChangeUserPassword ... +func ChangeUserPassword(userID string, opt model.ChangePasswordOptions) error { + var ( + ctx = context.Background() + ) + + // Validate payload + err := opt.Validate() + if err != nil { + return err + } + + // Validate userID + if _, isValid := mongodb.NewIDFromString(userID); !isValid { + logger.Error("usermngmt - ChangePassword: invalid userID data", logger.LogData{ + "payload": opt, + "userID": userID, + }) + return errors.New("invalid user id data") + } + + // Find user + id, _ := mongodb.NewIDFromString(userID) + user, _ := findByID(ctx, id) + if user.ID.IsZero() { + return errors.New("user not found") + } + + // Check old password + if isValid := internal.CheckPasswordHash(opt.OldPassword, user.HashedPassword); !isValid { + return errors.New("the password is incorrect") + } + + // Update password + if err = updateOneByCondition(ctx, bson.M{"_id": user.ID}, bson.M{ + "$set": bson.M{ + "hashedPassword": internal.HashPassword(opt.NewPassword), + "updatedAt": internal.Now(), + }, + }); err != nil { + return err + } + + return nil +} + +// ChangeUserStatus ... +func ChangeUserStatus(userID, newStatus string) error { + var ( + ctx = context.Background() + ) + + // Validate userID + id, isValid := mongodb.NewIDFromString(userID) + if !isValid { + return errors.New("invalid user id data") + } + + // Update status + if err := updateOneByCondition(ctx, bson.M{"_id": id}, bson.M{ + "$set": bson.M{ + "status": newStatus, + "updatedAt": internal.Now(), + }, + }); err != nil { + return err + } + + return nil +} diff --git a/usermngmt.go b/usermngmt.go index 5979cff..b2a2236 100644 --- a/usermngmt.go +++ b/usermngmt.go @@ -5,7 +5,8 @@ import ( "fmt" "github.com/Selly-Modules/mongodb" - "go.mongodb.org/mongo-driver/mongo" + "github.com/Selly-Modules/usermngmt/database" + "github.com/Selly-Modules/usermngmt/internal" ) // MongoDBConfig ... @@ -23,8 +24,7 @@ type Config struct { // Service ... type Service struct { - Config - DB *mongo.Database + config Config } var s *Service @@ -37,7 +37,7 @@ func Init(config Config) (*Service, error) { // If prefixTable is empty then it is usermngmt if config.TablePrefix == "" { - config.TablePrefix = tablePrefixDefault + config.TablePrefix = internal.TablePrefixDefault } // Connect MongoDB @@ -54,9 +54,11 @@ func Init(config Config) (*Service, error) { return nil, err } + // Set database + database.Set(db, config.TablePrefix) + s = &Service{ - Config: config, - DB: db, + config: config, } return s, nil