Skip to content

Commit

Permalink
refactor(context): change updates in user module
Browse files Browse the repository at this point in the history
  • Loading branch information
bastean committed Jun 10, 2024
1 parent 8ccc22c commit ba294af
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 88 deletions.
35 changes: 33 additions & 2 deletions pkg/context/user/application/update/command.handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,45 @@ import (
"github.com/bastean/codexgo/pkg/context/shared/domain/errors"
"github.com/bastean/codexgo/pkg/context/shared/domain/models"
"github.com/bastean/codexgo/pkg/context/shared/domain/types"
"github.com/bastean/codexgo/pkg/context/user/domain/aggregate"
"github.com/bastean/codexgo/pkg/context/user/domain/valueobj"
)

type Input struct {
User *aggregate.User
UpdatedPassword models.ValueObject[string]
}

type CommandHandler struct {
models.UseCase[*Command, types.Empty]
models.UseCase[*Input, types.Empty]
}

func (handler *CommandHandler) Handle(command *Command) error {
_, err := handler.UseCase.Run(command)
user, err := aggregate.NewUser(&aggregate.UserPrimitive{
Id: command.Id,
Email: command.Email,
Username: command.Username,
Password: command.Password,
})

if err != nil {
return errors.BubbleUp(err, "Handle")
}

var updatedPassword models.ValueObject[string]

if command.UpdatedPassword != "" {
updatedPassword, err = valueobj.NewPassword(command.UpdatedPassword)

if err != nil {
return errors.BubbleUp(err, "Handle")
}
}

_, err = handler.UseCase.Run(&Input{
User: user,
UpdatedPassword: updatedPassword,
})

if err != nil {
return errors.BubbleUp(err, "Handle")
Expand Down
4 changes: 2 additions & 2 deletions pkg/context/user/application/update/command.handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
type UserUpdateTestSuite struct {
suite.Suite
sut models.CommandHandler[*update.Command]
useCase models.UseCase[*update.Command, types.Empty]
useCase models.UseCase[*update.Input, types.Empty]
hashing *cryptographic.HashingMock
repository *persistence.RepositoryMock
}
Expand All @@ -44,7 +44,7 @@ func (suite *UserUpdateTestSuite) TestUpdate() {
Id: command.Id,
Email: command.Email,
Username: command.Username,
Password: command.Password,
Password: command.UpdatedPassword,
})

idVO, _ := valueobj.NewId(command.Id)
Expand Down
39 changes: 8 additions & 31 deletions pkg/context/user/application/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,35 @@ import (
"github.com/bastean/codexgo/pkg/context/shared/domain/types"
"github.com/bastean/codexgo/pkg/context/user/domain/model"
"github.com/bastean/codexgo/pkg/context/user/domain/service"
"github.com/bastean/codexgo/pkg/context/user/domain/valueobj"
)

type Update struct {
model.Repository
model.Hashing
}

func (update *Update) Run(userUpdate *Command) (types.Empty, error) {
idVO, err := valueobj.NewId(userUpdate.Id)

if err != nil {
return nil, errors.BubbleUp(err, "Run")
}

userRegistered, err := update.Repository.Search(&model.RepositorySearchCriteria{
Id: idVO,
func (update *Update) Run(input *Input) (types.Empty, error) {
user, err := update.Repository.Search(&model.RepositorySearchCriteria{
Id: input.User.Id,
})

if err != nil {
return nil, errors.BubbleUp(err, "Run")
}

err = service.IsPasswordInvalid(update.Hashing, userRegistered.Password.Value(), userUpdate.Password)
err = service.IsPasswordInvalid(update.Hashing, user.Password.Value(), input.User.Password.Value())

if err != nil {
return nil, errors.BubbleUp(err, "Run")
}

var errEmail, errUsername, errPassword error

if userUpdate.Email != "" {
userRegistered.Email, errEmail = valueobj.NewEmail(userUpdate.Email)
}

if userUpdate.Username != "" {
userRegistered.Username, errUsername = valueobj.NewUsername(userUpdate.Username)
}

if userUpdate.UpdatedPassword != "" {
userRegistered.Password, errPassword = valueobj.NewPassword(userUpdate.UpdatedPassword)
} else {
userRegistered.Password = nil
if input.UpdatedPassword != nil {
input.User.Password = input.UpdatedPassword
}

err = errors.Join(errEmail, errUsername, errPassword)

if err != nil {
return nil, errors.BubbleUp(err, "Run")
}
input.User.Verified = user.Verified

err = update.Repository.Update(userRegistered)
err = update.Repository.Update(input.User)

if err != nil {
return nil, errors.BubbleUp(err, "Run")
Expand Down
4 changes: 1 addition & 3 deletions pkg/context/user/application/verify/command.handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@ func (suite *UserVerifyTestSuite) TestVerify() {

user.Id = idVO

user.Password = nil

criteria := &model.RepositorySearchCriteria{
Id: idVO,
}

suite.repository.On("Search", criteria).Return(user)

suite.repository.On("Update", user)
suite.repository.On("Verify", idVO)

suite.NoError(suite.sut.Handle(command))

Expand Down
15 changes: 3 additions & 12 deletions pkg/context/user/application/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,26 @@ import (
"github.com/bastean/codexgo/pkg/context/shared/domain/models"
"github.com/bastean/codexgo/pkg/context/shared/domain/types"
"github.com/bastean/codexgo/pkg/context/user/domain/model"
"github.com/bastean/codexgo/pkg/context/user/domain/valueobj"
)

type Verify struct {
model.Repository
}

func (verify *Verify) Run(id models.ValueObject[string]) (types.Empty, error) {
userRegistered, err := verify.Repository.Search(&model.RepositorySearchCriteria{
user, err := verify.Repository.Search(&model.RepositorySearchCriteria{
Id: id,
})

if err != nil {
return nil, errors.BubbleUp(err, "Run")
}

if userRegistered.Verified.Value() {
if user.Verified.Value() {
return nil, nil
}

userRegistered.Verified, err = valueobj.NewVerified(true)

if err != nil {
return nil, errors.BubbleUp(err, "Run")
}

userRegistered.Password = nil

err = verify.Repository.Update(userRegistered)
err = verify.Repository.Verify(id)

if err != nil {
return nil, errors.BubbleUp(err, "Run")
Expand Down
1 change: 1 addition & 0 deletions pkg/context/user/domain/model/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type RepositorySearchCriteria struct {

type Repository interface {
Save(user *aggregate.User) error
Verify(id models.ValueObject[string]) error
Update(user *aggregate.User) error
Delete(id models.ValueObject[string]) error
Search(criteria *RepositorySearchCriteria) (*aggregate.User, error)
Expand Down
82 changes: 45 additions & 37 deletions pkg/context/user/infrastructure/persistence/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
)

type UserDocument struct {
Id string `bson:"id"`
Email string `bson:"email"`
Username string `bson:"username"`
Password string `bson:"password"`
Verified bool `bson:"verified"`
Id string `bson:"id,omitempty"`
Email string `bson:"email,omitempty"`
Username string `bson:"username,omitempty"`
Password string `bson:"password,omitempty"`
Verified bool `bson:"verified,omitempty"`
}

type UserCollection struct {
Expand Down Expand Up @@ -57,34 +57,43 @@ func (db *UserCollection) Save(user *aggregate.User) error {
return nil
}

func (db *UserCollection) Update(user *aggregate.User) error {
updateFilter := bson.M{"id": user.Id.Value()}
func (db *UserCollection) Verify(id models.ValueObject[string]) error {
filter := bson.D{{Key: "id", Value: id.Value()}}

updateUser := bson.M{}
_, err := db.collection.UpdateOne(context.Background(), filter, bson.D{
{Key: "$set", Value: bson.D{
{Key: "verified", Value: true},
}},
})

if user.Email != nil {
updateUser["email"] = user.Email.Value()
if err != nil {
return errors.NewInternal(&errors.Bubble{
Where: "Verify",
What: "failure to verify a user",
Why: errors.Meta{
"Id": id.Value(),
},
Who: err,
})
}

if user.Username != nil {
updateUser["username"] = user.Username.Value()
}
return nil
}

if user.Password != nil {
hashed, err := db.hashing.Hash(user.Password.Value())
func (db *UserCollection) Update(user *aggregate.User) error {
updatedUser := UserDocument(*user.ToPrimitives())

if err != nil {
return errors.BubbleUp(err, "Update")
}
filter := bson.D{{Key: "id", Value: user.Id.Value()}}

updateUser["password"] = hashed
}
hashed, err := db.hashing.Hash(user.Password.Value())

if user.Verified != nil {
updateUser["verified"] = user.Verified.Value()
if err != nil {
return errors.BubbleUp(err, "Update")
}

_, err := db.collection.UpdateOne(context.Background(), updateFilter, bson.M{"$set": updateUser})
updatedUser.Password = hashed

_, err = db.collection.ReplaceOne(context.Background(), filter, updatedUser)

if err != nil {
return errors.NewInternal(&errors.Bubble{
Expand All @@ -101,9 +110,9 @@ func (db *UserCollection) Update(user *aggregate.User) error {
}

func (db *UserCollection) Delete(id models.ValueObject[string]) error {
deleteFilter := bson.M{"id": id.Value()}
filter := bson.D{{Key: "id", Value: id.Value()}}

_, err := db.collection.DeleteOne(context.Background(), deleteFilter)
_, err := db.collection.DeleteOne(context.Background(), filter)

if err != nil {
return errors.NewInternal(&errors.Bubble{
Expand All @@ -120,30 +129,29 @@ func (db *UserCollection) Delete(id models.ValueObject[string]) error {
}

func (db *UserCollection) Search(criteria *model.RepositorySearchCriteria) (*aggregate.User, error) {
var searchFilter bson.M
var filter bson.D
var index string

if criteria.Email != nil {
searchFilter = bson.M{"email": criteria.Email.Value()}
index = criteria.Email.Value()
}

if criteria.Id != nil {
searchFilter = bson.M{"id": criteria.Id.Value()}
switch {
case criteria.Id != nil:
filter = bson.D{{Key: "id", Value: criteria.Id.Value()}}
index = criteria.Id.Value()
case criteria.Email != nil:
filter = bson.D{{Key: "email", Value: criteria.Email.Value()}}
index = criteria.Email.Value()
}

result := db.collection.FindOne(context.Background(), searchFilter)
result := db.collection.FindOne(context.Background(), filter)

if err := result.Err(); err != nil {
return nil, persistences.HandleMongoDocumentNotFound(index, err)
}

userPrimitive := new(aggregate.UserPrimitive)
primitive := new(aggregate.UserPrimitive)

result.Decode(userPrimitive)
result.Decode(primitive)

user, err := aggregate.FromPrimitives(userPrimitive)
user, err := aggregate.FromPrimitives(primitive)

if err != nil {
return nil, errors.NewInternal(&errors.Bubble{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ func (suite *UserMongoRepositoryTestSuite) TestSaveDuplicate() {
suite.Error(suite.sut.Save(user))
}

func (suite *UserMongoRepositoryTestSuite) TestVerify() {
user := aggregate.RandomUser()

suite.hashing.On("Hash", user.Password.Value()).Return(user.Password.Value())

suite.NoError(suite.sut.Save(user))

suite.NoError(suite.sut.Verify(user.Id))
}

func (suite *UserMongoRepositoryTestSuite) TestUpdate() {
user := aggregate.RandomUser()

Expand Down Expand Up @@ -91,7 +101,7 @@ func (suite *UserMongoRepositoryTestSuite) TestSearch() {
suite.NoError(suite.sut.Save(expected))

criteria := &model.RepositorySearchCriteria{
Email: expected.Email,
Id: expected.Id,
}

user, err := suite.sut.Search(criteria)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ func (repository *RepositoryMock) Save(user *aggregate.User) error {
return nil
}

func (repository *RepositoryMock) Verify(id models.ValueObject[string]) error {
repository.Called(id)
return nil
}

func (repository *RepositoryMock) Update(user *aggregate.User) error {
repository.Called(user)
return nil
Expand Down

0 comments on commit ba294af

Please sign in to comment.