diff --git a/action.go b/action.go new file mode 100644 index 0000000..fb41213 --- /dev/null +++ b/action.go @@ -0,0 +1,30 @@ +package usermngmt + +import "github.com/Selly-Modules/usermngmt/internal" + +// Create ... +func (s Service) Create(payload internal.CreateOptions) error { + return s.userHandle().Create(payload) +} + +// Update ... +func (s Service) Update(userID string, payload internal.UpdateOptions) error { + return s.userHandle().UpdateByUserID(userID, payload) +} + +// ChangeUserPassword ... +func (s Service) ChangeUserPassword(userID string, payload internal.ChangePasswordOptions) error { + return s.userHandle().ChangeUserPassword(userID, payload) +} + +func (s Service) ChangeUserStatus(userID, newStatus string) error { + return s.userHandle().ChangeUserStatus(userID, newStatus) +} + +func (s Service) All(query internal.AllQuery) internal.UserAll { + return s.userHandle().All(query) +} + +func (s Service) RoleCreate(payload internal.RoleCreateOptions) error { + return s.roleHandle().Create(payload) +} diff --git a/action_create.go b/action_create.go deleted file mode 100644 index d09d1af..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 dbUser, err error) { - timeNow := now() - roleID, _ := mongodb.NewIDFromString(payload.RoleID) - return dbUser{ - 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 a169559..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 UserAll) { - 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 []dbUser) []User { - res := make([]User, 0) - - for _, user := range users { - role, _ := s.roleFindByID(ctx, user.RoleID) - res = append(res, User{ - 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 index ab8a97d..4252788 100644 --- a/constant.go +++ b/constant.go @@ -5,8 +5,4 @@ const ( tableUser = "users" tableRole = "roles" tablePrefixDefault = "usermngmt" - - timezoneHCM = "Asia/Ho_Chi_Minh" - - passwordHashingCost = 14 ) diff --git a/db.go b/db.go index 0e88f3e..ef436a8 100644 --- a/db.go +++ b/db.go @@ -1,14 +1,9 @@ package usermngmt import ( - "context" "fmt" - "github.com/Selly-Modules/logger" - "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 ... @@ -20,146 +15,3 @@ func (s Service) getUserCollection() *mongo.Collection { 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 { - var ( - col = s.getUserCollection() - ) - - // Find - cond := bson.M{ - "$or": []bson.M{ - { - "phone": phone, - }, - { - "email": email, - }, - }, - } - total, err := col.CountDocuments(ctx, cond) - if err != nil { - logger.Error("usermngmt - countUserByCondition", logger.LogData{ - "condition": cond, - "err": err.Error(), - }) - return true - } - return total != 0 -} - -func (s Service) isRoleIDExisted(ctx context.Context, roleID primitive.ObjectID) bool { - var ( - col = s.getRoleCollection() - ) - - // Find - cond := bson.M{ - "_id": roleID, - } - total, err := col.CountDocuments(ctx, cond) - if err != nil { - logger.Error("usermngmt - countRoleByCondition", logger.LogData{ - "condition": cond, - "err": err.Error(), - }) - return false - } - return total != 0 -} - -func (s Service) userCreate(ctx context.Context, doc dbUser) error { - var ( - col = s.getUserCollection() - ) - - _, err := col.InsertOne(ctx, doc) - if err != nil { - logger.Error("usermngmt - Create", logger.LogData{ - "doc": doc, - "err": err.Error(), - }) - return fmt.Errorf("error when create user: %s", err.Error()) - } - - return nil -} - -func (s Service) userUpdateOneByCondition(ctx context.Context, cond interface{}, payload interface{}) error { - var ( - col = s.getUserCollection() - ) - - _, err := col.UpdateOne(ctx, cond, payload) - if err != nil { - logger.Error("usermngmt - Update", logger.LogData{ - "cond": cond, - "payload": payload, - "err": err.Error(), - }) - return fmt.Errorf("error when update user: %s", err.Error()) - } - - return err -} - -func (s Service) userFindByID(ctx context.Context, id primitive.ObjectID) (dbUser, error) { - var ( - col = s.getUserCollection() - doc dbUser - ) - 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 []dbUser) { - var ( - col = s.getUserCollection() - ) - docs = make([]dbUser, 0) - - cursor, err := col.Find(ctx, cond, opts...) - if err != nil { - logger.Error("usermngmt - All", logger.LogData{ - "cond": cond, - "opts": opts, - "err": err.Error(), - }) - return - } - defer cursor.Close(ctx) - if err = cursor.All(ctx, &docs); err != nil { - logger.Error("usermngmt - All - decode", logger.LogData{ - "cond": cond, - "opts": opts, - "err": err.Error(), - }) - return - } - return -} - -// userCountByCondition ... -func (s Service) userCountByCondition(ctx context.Context, cond interface{}) int64 { - var ( - col = s.getUserCollection() - ) - - total, err := col.CountDocuments(ctx, cond) - if err != nil { - logger.Error("usermngmt - Count", logger.LogData{ - "err": err.Error(), - "cond": cond, - }) - } - return total -} - -func (s Service) roleFindByID(ctx context.Context, id primitive.ObjectID) (dbRole, error) { - var ( - col = s.getRoleCollection() - doc dbRole - ) - err := col.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) - return doc, err -} 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/init_handle.go b/init_handle.go new file mode 100644 index 0000000..30816b3 --- /dev/null +++ b/init_handle.go @@ -0,0 +1,21 @@ +package usermngmt + +import ( + "github.com/Selly-Modules/usermngmt/role" + "github.com/Selly-Modules/usermngmt/user" +) + +// userHandle ... +func (s Service) userHandle() user.Handle { + return user.Handle{ + Col: s.getUserCollection(), + RoleCol: s.getRoleCollection(), + } +} + +// roleHandle ... +func (s Service) roleHandle() role.Handle { + return role.Handle{ + Col: s.getRoleCollection(), + } +} diff --git a/internal/constant.go b/internal/constant.go new file mode 100644 index 0000000..7a5b32b --- /dev/null +++ b/internal/constant.go @@ -0,0 +1,9 @@ +package internal + +// Constant ... +const ( + timezoneHCM = "Asia/Ho_Chi_Minh" + + passwordHashingCost = 14 +) + diff --git a/db_model.go b/internal/db_model.go similarity index 92% rename from db_model.go rename to internal/db_model.go index 2eb0465..8b183bb 100644 --- a/db_model.go +++ b/internal/db_model.go @@ -1,4 +1,4 @@ -package usermngmt +package internal import ( "time" @@ -6,8 +6,18 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// dbUser ... -type dbUser struct { +// 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"` @@ -21,12 +31,3 @@ type dbUser struct { UpdatedAt time.Time `bson:"updatedAt"` } -// 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"` -} diff --git a/helper.go b/internal/helper.go similarity index 77% rename from helper.go rename to internal/helper.go index fbee0db..584a13d 100644 --- a/helper.go +++ b/internal/helper.go @@ -1,4 +1,4 @@ -package usermngmt +package internal import ( "fmt" @@ -7,17 +7,17 @@ import ( "golang.org/x/crypto/bcrypt" ) -func hashPassword(password string) string { +func HashPassword(password string) string { bytes, _ := bcrypt.GenerateFromPassword([]byte(password), passwordHashingCost) return string(bytes) } -func checkPasswordHash(password, hash string) bool { +func CheckPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } -func getSearchString(fieldList ...string) string { +func GetSearchString(fieldList ...string) string { var ( searchList = make([]interface{}, 0) format = "" @@ -33,3 +33,4 @@ func getSearchString(fieldList ...string) string { } return fmt.Sprintf(format, searchList...) } + diff --git a/query.go b/internal/query.go similarity index 73% rename from query.go rename to internal/query.go index 704edfa..d4bf9a7 100644 --- a/query.go +++ b/internal/query.go @@ -1,4 +1,4 @@ -package usermngmt +package internal import ( "github.com/Selly-Modules/mongodb" @@ -6,7 +6,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) -type commonQuery struct { +type CommonQuery struct { Page int64 Limit int64 Keyword string @@ -16,14 +16,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 +32,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,8 +51,9 @@ 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/internal/role_model.go b/internal/role_model.go new file mode 100644 index 0000000..6db9c33 --- /dev/null +++ b/internal/role_model.go @@ -0,0 +1,12 @@ +package internal + +type RoleShort struct { + ID string `json:"_id"` + Name string `json:"name"` + IsAdmin bool `json:"isAdmin"` +} + +// RoleCreateOptions ... +type RoleCreateOptions struct { + Name string +} diff --git a/time.go b/internal/time.go similarity index 88% rename from time.go rename to internal/time.go index 8e31061..f1b88a1 100644 --- a/time.go +++ b/internal/time.go @@ -1,4 +1,4 @@ -package usermngmt +package internal import "time" @@ -14,7 +14,8 @@ func getHCMLocation() *time.Location { return l } -// now ... -func now() time.Time { +// Now ... +func Now() time.Time { return time.Now().In(getHCMLocation()) } + diff --git a/internal/user_model.go b/internal/user_model.go new file mode 100644 index 0000000..333ae7a --- /dev/null +++ b/internal/user_model.go @@ -0,0 +1,187 @@ +package internal + +import ( + "errors" + "time" + + "github.com/Selly-Modules/logger" + "github.com/Selly-Modules/mongodb" +) + +// CreateOptions ... +type CreateOptions struct { + Name string + Phone string + Email string + Password string + Status string + RoleID string + Other string +} + +// UpdateOptions ... +type UpdateOptions struct { + Name string + Phone string + Email string + RoleID string + Other string +} + +// ChangePasswordOptions ... +type ChangePasswordOptions struct { + OldPassword string + NewPassword string +} + +// AllQuery ... +type AllQuery struct { + Page int64 + Limit int64 + Keyword string + RoleID string + Status string +} + +// 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"` + } +) + +// NewUser ... +func (payload CreateOptions) NewUser() (result DBUser, err error) { + timeNow := Now() + roleID, _ := mongodb.NewIDFromString(payload.RoleID) + return DBUser{ + 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 +} + +// Validate ... +func (co CreateOptions) Validate() error { + // Name + if co.Name == "" { + logger.Error("usermngmt - Create: no Name data", logger.LogData{ + "payload": co, + }) + return errors.New("no name data") + } + + // Phone + if co.Phone == "" { + logger.Error("usermngmt - Create: no phone data", logger.LogData{ + "payload": co, + }) + return errors.New("no phone data") + } + + // Email + if co.Email == "" { + logger.Error("usermngmt - Create: no email data", logger.LogData{ + "payload": co, + }) + return errors.New("no email data") + } + + // Password + if co.Password == "" { + logger.Error("usermngmt - Create: no password data", logger.LogData{ + "payload": co, + }) + return errors.New("no password data") + } + + // Status + if co.Status == "" { + logger.Error("usermngmt - Create: no status data", logger.LogData{ + "payload": co, + }) + return errors.New("no status data") + } + + // RoleID + if co.RoleID == "" { + logger.Error("usermngmt - Create: no roleID data", logger.LogData{ + "payload": co, + }) + return errors.New("no role id data") + } + + return nil +} + +// Validate ... +func (uo UpdateOptions) Validate() error { + // Name + if uo.Name == "" { + logger.Error("usermngmt - Update: no name data", logger.LogData{ + "payload": uo, + }) + return errors.New("no name data") + } + + // Phone + if uo.Phone == "" { + logger.Error("usermngmt - Update: no phone data", logger.LogData{ + "payload": uo, + }) + return errors.New("no phone data") + } + + // Email + if uo.Email == "" { + logger.Error("usermngmt - Update: no email data", logger.LogData{ + "payload": uo, + }) + return errors.New("no email data") + } + + // RoleID + if uo.RoleID == "" { + logger.Error("usermngmt - Update: no roleID data", logger.LogData{ + "payload": uo, + }) + return errors.New("no role id data") + } + + return nil +} + +// 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{ + "payload": co, + }) + return errors.New("old or new password cannot be empty") + } + + return nil +} diff --git a/model.go b/model.go deleted file mode 100644 index 7fe6391..0000000 --- a/model.go +++ /dev/null @@ -1,32 +0,0 @@ -package usermngmt - -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 RoleShort struct { - ID string `json:"_id"` - Name string `json:"name"` - IsAdmin bool `json:"isAdmin"` -} - -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..2a7793c --- /dev/null +++ b/role/db.go @@ -0,0 +1,17 @@ +package role + +import ( + "context" + + "github.com/Selly-Modules/usermngmt/internal" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func (h Handle) findByID(ctx context.Context, id primitive.ObjectID) (internal.DBRole, error) { + var ( + doc internal.DBRole + ) + err := h.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..0178083 --- /dev/null +++ b/role/handle.go @@ -0,0 +1,25 @@ +package role + +import ( + "context" + + "github.com/Selly-Modules/usermngmt/internal" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type Handle struct { + Col *mongo.Collection +} + +// FindByID ... +func (h Handle) FindByID(ctx context.Context, id primitive.ObjectID) (internal.DBRole, error) { + role, err := h.findByID(ctx, id) + return role, err +} + +// Create ... +func (h Handle) Create(payload internal.RoleCreateOptions) error { + // TODO later + return nil +} diff --git a/user/db.go b/user/db.go new file mode 100644 index 0000000..6316ab2 --- /dev/null +++ b/user/db.go @@ -0,0 +1,130 @@ +package user + +import ( + "context" + "fmt" + + "github.com/Selly-Modules/logger" + "github.com/Selly-Modules/usermngmt/internal" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func (h Handle) isPhoneNumberOrEmailExisted(ctx context.Context, phone, email string) bool { + // Find + cond := bson.M{ + "$or": []bson.M{ + { + "phone": phone, + }, + { + "email": email, + }, + }, + } + total, err := h.Col.CountDocuments(ctx, cond) + if err != nil { + logger.Error("usermngmt - countUserByCondition", logger.LogData{ + "condition": cond, + "err": err.Error(), + }) + return true + } + return total != 0 +} + +func (h Handle) isRoleIDExisted(ctx context.Context, roleID primitive.ObjectID) bool { + // Find + cond := bson.M{ + "_id": roleID, + } + total, err := h.RoleCol.CountDocuments(ctx, cond) + if err != nil { + logger.Error("usermngmt - countRoleByCondition", logger.LogData{ + "condition": cond, + "err": err.Error(), + }) + return false + } + return total != 0 +} + +func (h Handle) roleFindByID(ctx context.Context, id primitive.ObjectID) (internal.DBRole, error) { + var ( + doc internal.DBRole + ) + err := h.RoleCol.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) + return doc, err +} + +func (h Handle) create(ctx context.Context, doc internal.DBUser) error { + _, err := h.Col.InsertOne(ctx, doc) + if err != nil { + logger.Error("usermngmt - Create", logger.LogData{ + "doc": doc, + "err": err.Error(), + }) + return fmt.Errorf("error when create user: %s", err.Error()) + } + + return nil +} + +func (h Handle) updateOneByCondition(ctx context.Context, cond interface{}, payload interface{}) error { + _, err := h.Col.UpdateOne(ctx, cond, payload) + if err != nil { + logger.Error("usermngmt - Update", logger.LogData{ + "cond": cond, + "payload": payload, + "err": err.Error(), + }) + return fmt.Errorf("error when update user: %s", err.Error()) + } + + return err +} + +func (h Handle) findByID(ctx context.Context, id primitive.ObjectID) (internal.DBUser, error) { + var ( + doc internal.DBUser + ) + err := h.Col.FindOne(ctx, bson.M{"_id": id}).Decode(&doc) + return doc, err +} + +func (h Handle) findByCondition(ctx context.Context, cond interface{}, opts ...*options.FindOptions) (docs []internal.DBUser) { + docs = make([]internal.DBUser, 0) + + cursor, err := h.Col.Find(ctx, cond, opts...) + if err != nil { + logger.Error("usermngmt - All", logger.LogData{ + "cond": cond, + "opts": opts, + "err": err.Error(), + }) + return + } + defer cursor.Close(ctx) + if err = cursor.All(ctx, &docs); err != nil { + logger.Error("usermngmt - All - decode", logger.LogData{ + "cond": cond, + "opts": opts, + "err": err.Error(), + }) + return + } + return +} + +// countByCondition ... +func (h Handle) countByCondition(ctx context.Context, cond interface{}) int64 { + total, err := h.Col.CountDocuments(ctx, cond) + if err != nil { + logger.Error("usermngmt - Count", logger.LogData{ + "err": err.Error(), + "cond": cond, + }) + } + return total +} diff --git a/user/handle.go b/user/handle.go new file mode 100644 index 0000000..8bf935c --- /dev/null +++ b/user/handle.go @@ -0,0 +1,245 @@ +package user + +import ( + "context" + "errors" + "sync" + + "github.com/Selly-Modules/logger" + "github.com/Selly-Modules/mongodb" + "github.com/Selly-Modules/usermngmt/internal" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type Handle struct { + Col *mongo.Collection + RoleCol *mongo.Collection +} + +// Create ... +func (h Handle) Create(payload internal.CreateOptions) 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 !h.isRoleIDExisted(ctx, roleID) { + return errors.New("role id does not exist") + } + + // Find phone number,email exists or not + if h.isPhoneNumberOrEmailExisted(ctx, payload.Phone, payload.Email) { + return errors.New("phone number or email already existed") + } + + // New user data from payload + doc, err := payload.NewUser() + if err != nil { + return err + } + + // Create user + if err = h.create(ctx, doc); err != nil { + return err + } + + return nil +} + +// All ... +func (h Handle) All(queryParams internal.AllQuery) (r internal.UserAll) { + var ( + ctx = context.Background() + wg sync.WaitGroup + cond = bson.M{} + ) + query := internal.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 := h.findByCondition(ctx, cond, query.GetFindOptionsUsingPage()) + r.List = h.getResponseList(ctx, docs) + }() + + wg.Add(1) + go func() { + defer wg.Done() + r.Total = h.countByCondition(ctx, cond) + }() + + wg.Wait() + + return +} + +func (h Handle) getResponseList(ctx context.Context, users []internal.DBUser) []internal.User { + res := make([]internal.User, 0) + + for _, user := range users { + roleRaw, _ := h.roleFindByID(ctx, user.RoleID) + res = append(res, internal.User{ + ID: user.ID.Hex(), + Name: user.Name, + Phone: user.Phone, + Email: user.Email, + Status: user.Status, + Role: internal.RoleShort{ + ID: roleRaw.ID.Hex(), + Name: roleRaw.Name, + IsAdmin: roleRaw.IsAdmin, + }, + Other: user.Other, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }) + } + + return res +} + +// UpdateByUserID ... +func (h Handle) UpdateByUserID(userID string, payload internal.UpdateOptions) 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 !h.isRoleIDExisted(ctx, roleID) { + return errors.New("role id does not exist") + } + + // Find phone number,email exists or not + if h.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 := h.updateOneByCondition(ctx, cond, updateData); err != nil { + return err + } + + return nil +} + +// ChangeUserPassword ... +func (h Handle) ChangeUserPassword(userID string, opt internal.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, _ := h.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 = h.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 (h Handle) 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 := h.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/validate.go b/validate.go deleted file mode 100644 index a856ab3..0000000 --- a/validate.go +++ /dev/null @@ -1,145 +0,0 @@ -package usermngmt - -import ( - "context" - "errors" - - "github.com/Selly-Modules/logger" - "github.com/Selly-Modules/mongodb" -) - -func (co CreateOptions) validate(ctx context.Context) error { - // Name - if co.Name == "" { - logger.Error("usermngmt - Create: no Name data", logger.LogData{ - "payload": co, - }) - return errors.New("no name data") - } - - // Phone - if co.Phone == "" { - logger.Error("usermngmt - Create: no phone data", logger.LogData{ - "payload": co, - }) - return errors.New("no phone data") - } - - // Email - if co.Email == "" { - logger.Error("usermngmt - Create: no email data", logger.LogData{ - "payload": co, - }) - return errors.New("no email data") - } - - // Password - if co.Password == "" { - logger.Error("usermngmt - Create: no password data", logger.LogData{ - "payload": co, - }) - return errors.New("no password data") - } - - // Status - if co.Status == "" { - logger.Error("usermngmt - Create: no status data", logger.LogData{ - "payload": co, - }) - return errors.New("no status data") - } - - // RoleID - if co.RoleID == "" { - logger.Error("usermngmt - Create: no roleID data", logger.LogData{ - "payload": co, - }) - 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 { - // Name - if co.Name == "" { - logger.Error("usermngmt - Update: no name data", logger.LogData{ - "payload": co, - }) - return errors.New("no name data") - } - - // Phone - if co.Phone == "" { - logger.Error("usermngmt - Update: no phone data", logger.LogData{ - "payload": co, - }) - return errors.New("no phone data") - } - - // Email - if co.Email == "" { - logger.Error("usermngmt - Update: no email data", logger.LogData{ - "payload": co, - }) - return errors.New("no email data") - } - - // RoleID - if co.RoleID == "" { - logger.Error("usermngmt - Update: no roleID data", logger.LogData{ - "payload": co, - }) - 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 { - // OldPassword, NewPassword - if co.OldPassword == "" || co.NewPassword == "" { - logger.Error("usermngmt - ChangePassword: old or new password cannot be empty", logger.LogData{ - "payload": co, - }) - 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 -}