diff --git a/internal/howmuch/adapter/repo/sqlc/user.sql b/internal/howmuch/adapter/repo/sqlc/user.sql index a14d9a3..bd7a095 100644 --- a/internal/howmuch/adapter/repo/sqlc/user.sql +++ b/internal/howmuch/adapter/repo/sqlc/user.sql @@ -25,3 +25,8 @@ INSERT INTO "user" ( email, first_name, last_name, password, created_at, updated_at ) VALUES ( $1, $2, $3, $4, $5, $6 ) RETURNING *; + +-- name: GetUserByEmail :one +SELECT id, email, first_name, last_name, password, created_at, updated_at + FROM "user" + WHERE email = $1; diff --git a/internal/howmuch/adapter/repo/sqlc/user.sql.go b/internal/howmuch/adapter/repo/sqlc/user.sql.go index c32e793..6e9a1da 100644 --- a/internal/howmuch/adapter/repo/sqlc/user.sql.go +++ b/internal/howmuch/adapter/repo/sqlc/user.sql.go @@ -11,6 +11,27 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const getUserByEmail = `-- name: GetUserByEmail :one +SELECT id, email, first_name, last_name, password, created_at, updated_at + FROM "user" + WHERE email = $1 +` + +func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) { + row := q.db.QueryRow(ctx, getUserByEmail, email) + var i User + err := row.Scan( + &i.ID, + &i.Email, + &i.FirstName, + &i.LastName, + &i.Password, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const insertUser = `-- name: InsertUser :one INSERT INTO "user" ( diff --git a/internal/howmuch/adapter/repo/user.go b/internal/howmuch/adapter/repo/user.go index 80a48bc..a273ced 100644 --- a/internal/howmuch/adapter/repo/user.go +++ b/internal/howmuch/adapter/repo/user.go @@ -86,3 +86,25 @@ func (ur *userRepository) Create( UpdatedAt: userDB.CreatedAt.Time, }, nil } + +// GetByEmail if not found, return nil for user but not error. +func (ur *userRepository) GetByEmail(ctx context.Context, email string) (*model.User, error) { + queries := sqlc.New(ur.db) + userDB, err := queries.GetUserByEmail(ctx, email) + if errors.Is(err, pgx.ErrNoRows) { + // No query error, but user not found + return nil, nil + } else if err != nil { + return nil, err + } + + return &model.User{ + ID: int(userDB.ID), + Email: userDB.Email, + FirstName: userDB.FirstName, + LastName: userDB.LastName, + Password: userDB.Password, + CreatedAt: userDB.CreatedAt.Time, + UpdatedAt: userDB.CreatedAt.Time, + }, nil +} diff --git a/internal/howmuch/usecase/repo/testuser.go b/internal/howmuch/usecase/repo/testuser.go index c108c68..eea2eab 100644 --- a/internal/howmuch/usecase/repo/testuser.go +++ b/internal/howmuch/usecase/repo/testuser.go @@ -27,8 +27,11 @@ import ( "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" "github.com/pkg/errors" + "golang.org/x/crypto/bcrypt" ) +var UserTestDummyErr = errors.New("dummy error") + type TestUserRepository struct{} func (tur *TestUserRepository) Create( @@ -46,3 +49,21 @@ func (tur *TestUserRepository) Create( return &user, nil } + +func (ur *TestUserRepository) GetByEmail(ctx context.Context, email string) (*model.User, error) { + hashedPwd, _ := bcrypt.GenerateFromPassword([]byte("strongHashed"), 12) + switch email { + case "a@b.c": + return &model.User{ + ID: 123, + Email: "a@b.c", + Password: string(hashedPwd), + }, nil + case "query@error.com": + return nil, UserTestDummyErr + case "inexist@error.com": + return nil, nil + } + + return nil, UserTestDummyErr +} diff --git a/internal/howmuch/usecase/repo/user.go b/internal/howmuch/usecase/repo/user.go index ea2abf0..5693b3a 100644 --- a/internal/howmuch/usecase/repo/user.go +++ b/internal/howmuch/usecase/repo/user.go @@ -30,4 +30,5 @@ import ( type UserRepository interface { Create(ctx context.Context, transaction interface{}, u *model.User) (*model.User, error) + GetByEmail(ctx context.Context, email string) (*model.User, error) } diff --git a/internal/howmuch/usecase/usecase/user.go b/internal/howmuch/usecase/usecase/user.go index 938ca40..0f3cb30 100644 --- a/internal/howmuch/usecase/usecase/user.go +++ b/internal/howmuch/usecase/usecase/user.go @@ -24,6 +24,7 @@ package usecase import ( "context" + "errors" "fmt" "net/http" "regexp" @@ -35,11 +36,23 @@ import ( "golang.org/x/crypto/bcrypt" ) -var UserExisted = &errno.Errno{ - HTTP: http.StatusBadRequest, - Code: errno.ErrorCode(errno.FailedOperationCode, "UserExisted"), - Message: "email already existed.", -} +var ( + UserExisted = &errno.Errno{ + HTTP: http.StatusBadRequest, + Code: errno.ErrorCode(errno.FailedOperationCode, "UserExisted"), + Message: "email already existed.", + } + UserNotExist = &errno.Errno{ + HTTP: http.StatusBadRequest, + Code: errno.ErrorCode(errno.ResourceNotFoundCode, "UserNotExist"), + Message: "user does not exists.", + } + UserWrongPassword = &errno.Errno{ + HTTP: http.StatusBadRequest, + Code: errno.ErrorCode(errno.AuthFailureCode, "UserWrongPassword"), + Message: "wrong password.", + } +) type userUsecase struct { userRepo repo.UserRepository @@ -48,6 +61,7 @@ type userUsecase struct { type User interface { Create(ctx context.Context, u *model.User) (*model.User, error) + Exist(ctx context.Context, u *model.User) (bool, error) } func NewUserUsecase(r repo.UserRepository, d repo.DBRepository) User { @@ -98,3 +112,24 @@ func (uuc *userUsecase) Create(ctx context.Context, u *model.User) (*model.User, return user, nil } + +func (uuc *userUsecase) Exist(ctx context.Context, u *model.User) (bool, error) { + got, err := uuc.userRepo.GetByEmail(ctx, u.Email) + // Any query error? + if err != nil { + return false, err + } + + // User exists? + if got == nil { + return false, UserNotExist + } + + // Password correct? + err = bcrypt.CompareHashAndPassword([]byte(got.Password), []byte(u.Password)) + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { + return false, UserWrongPassword + } + + return true, nil +} diff --git a/internal/howmuch/usecase/usecase/user_test.go b/internal/howmuch/usecase/usecase/user_test.go index 93a1263..70cfe89 100644 --- a/internal/howmuch/usecase/usecase/user_test.go +++ b/internal/howmuch/usecase/usecase/user_test.go @@ -63,3 +63,38 @@ func TestCreateUser(t *testing.T) { assert.EqualError(t, err, UserExisted.Error()) }) } + +func TestUserExist(t *testing.T) { + testCases := []struct { + Name string + User *model.User + ExpErr error + ExpRes bool + }{ + {"user exists", &model.User{ + Email: "a@b.c", + Password: "strongHashed", + }, nil, true}, + {"query error", &model.User{ + Email: "query@error.com", + Password: "strongHashed", + }, repo.UserTestDummyErr, false}, + {"user doesn not exist", &model.User{ + Email: "inexist@error.com", + Password: "strongHashed", + }, UserNotExist, false}, + {"wrong password", &model.User{ + Email: "a@b.c", + Password: "wrongHashed", + }, UserWrongPassword, false}, + } + + for _, tst := range testCases { + ctx := context.Background() + userUsecase := NewUserUsecase(&repo.TestUserRepository{}, &repo.TestDBRepository{}) + + got, err := userUsecase.Exist(ctx, tst.User) + assert.ErrorIs(t, err, tst.ExpErr) + assert.Equal(t, tst.ExpRes, got) + } +}