feat: add usecase to check if a user exists

This commit is contained in:
Muyao CHEN 2024-10-12 18:33:19 +02:00
parent c312b4e2c8
commit 3e09afd4b0
7 changed files with 145 additions and 5 deletions

View File

@ -25,3 +25,8 @@ INSERT INTO "user" (
email, first_name, last_name, password, created_at, updated_at email, first_name, last_name, password, created_at, updated_at
) VALUES ( $1, $2, $3, $4, $5, $6 ) ) VALUES ( $1, $2, $3, $4, $5, $6 )
RETURNING *; RETURNING *;
-- name: GetUserByEmail :one
SELECT id, email, first_name, last_name, password, created_at, updated_at
FROM "user"
WHERE email = $1;

View File

@ -11,6 +11,27 @@ import (
"github.com/jackc/pgx/v5/pgtype" "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 const insertUser = `-- name: InsertUser :one
INSERT INTO "user" ( INSERT INTO "user" (

View File

@ -86,3 +86,25 @@ func (ur *userRepository) Create(
UpdatedAt: userDB.CreatedAt.Time, UpdatedAt: userDB.CreatedAt.Time,
}, nil }, 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
}

View File

@ -27,8 +27,11 @@ import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
) )
var UserTestDummyErr = errors.New("dummy error")
type TestUserRepository struct{} type TestUserRepository struct{}
func (tur *TestUserRepository) Create( func (tur *TestUserRepository) Create(
@ -46,3 +49,21 @@ func (tur *TestUserRepository) Create(
return &user, nil 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
}

View File

@ -30,4 +30,5 @@ import (
type UserRepository interface { type UserRepository interface {
Create(ctx context.Context, transaction interface{}, u *model.User) (*model.User, error) Create(ctx context.Context, transaction interface{}, u *model.User) (*model.User, error)
GetByEmail(ctx context.Context, email string) (*model.User, error)
} }

View File

@ -24,6 +24,7 @@ package usecase
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
@ -35,11 +36,23 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
var UserExisted = &errno.Errno{ var (
UserExisted = &errno.Errno{
HTTP: http.StatusBadRequest, HTTP: http.StatusBadRequest,
Code: errno.ErrorCode(errno.FailedOperationCode, "UserExisted"), Code: errno.ErrorCode(errno.FailedOperationCode, "UserExisted"),
Message: "email already existed.", 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 { type userUsecase struct {
userRepo repo.UserRepository userRepo repo.UserRepository
@ -48,6 +61,7 @@ type userUsecase struct {
type User interface { type User interface {
Create(ctx context.Context, u *model.User) (*model.User, error) 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 { 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 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
}

View File

@ -63,3 +63,38 @@ func TestCreateUser(t *testing.T) {
assert.EqualError(t, err, UserExisted.Error()) 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)
}
}