Compare commits

..

No commits in common. "71926b2197bdb9967d9e97ce7ddc0d3aaf8d1879" and "addddb152af34c873e74c3f1260bd43d646f0af1" have entirely different histories.

9 changed files with 40 additions and 61 deletions

3
go.mod
View File

@ -3,6 +3,7 @@ module git.vinchent.xyz/vinchent/howmuch
go 1.23.1 go 1.23.1
require ( require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
@ -30,7 +31,7 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect

6
go.sum
View File

@ -1,3 +1,5 @@
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
@ -31,8 +33,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=

View File

@ -29,6 +29,7 @@ import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase" "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -57,26 +58,22 @@ func NewUserController(us usecase.User) User {
} }
func (uc *UserController) Create(ctx core.Context) { func (uc *UserController) Create(ctx core.Context) {
var params struct { var params model.User
Email string `json:"email" binding:"required,email"`
FirstName string `json:"first_name" binding:"required"`
LastName string `json:"last_name" binding:"required"`
Password string `json:"password" binding:"required"`
}
if err := ctx.Bind(&params); err != nil { if err := ctx.Bind(&params); err != nil {
core.WriteResponse(ctx, UserParamsErr, nil) core.WriteResponse(ctx, UserParamsErr, nil)
return return
} }
user := model.User{ _, err := govalidator.ValidateStruct(params)
Email: params.Email, if err != nil {
FirstName: params.FirstName, errno := UserParamsErr
LastName: params.LastName, errno.Message = err.Error()
Password: params.Password, core.WriteResponse(ctx, errno, nil)
return
} }
_, err := uc.userUsecase.Create(ctx, &user) _, err = uc.userUsecase.Create(ctx, &params)
if err != nil { if err != nil {
core.WriteResponse(ctx, err, nil) core.WriteResponse(ctx, err, nil)
return return

View File

@ -26,11 +26,11 @@ import "time"
// User model // User model
type User struct { type User struct {
ID int ID int `json:"id"`
Email string Email string `json:"email" valid:"email"`
FirstName string FirstName string `json:"first_name" valid:"required"`
LastName string LastName string `json:"last_name" valid:"required"`
Password string Password string `json:"password" valid:"required"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time UpdatedAt time.Time `json:"updated_at"`
} }

View File

@ -20,7 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
package repomock package usecase
import "context" import "context"

View File

@ -20,7 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
package repomock package usecase
import ( import (
"context" "context"

View File

@ -61,7 +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) 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 {
@ -113,23 +113,23 @@ 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) error { func (uuc *userUsecase) Exist(ctx context.Context, u *model.User) (bool, error) {
got, err := uuc.userRepo.GetByEmail(ctx, u.Email) got, err := uuc.userRepo.GetByEmail(ctx, u.Email)
// Any query error? // Any query error?
if err != nil { if err != nil {
return err return false, err
} }
// User exists? // User exists?
if got == nil { if got == nil {
return UserNotExist return false, UserNotExist
} }
// Password correct? // Password correct?
err = bcrypt.CompareHashAndPassword([]byte(got.Password), []byte(u.Password)) err = bcrypt.CompareHashAndPassword([]byte(got.Password), []byte(u.Password))
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return UserWrongPassword return false, UserWrongPassword
} }
return nil return true, nil
} }

View File

@ -27,14 +27,13 @@ import (
"testing" "testing"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase/repomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestCreateUser(t *testing.T) { func TestCreateUser(t *testing.T) {
t.Run("normal create", func(t *testing.T) { t.Run("normal create", func(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userUsecase := NewUserUsecase(&repomock.TestUserRepository{}, &repomock.TestDBRepository{}) userUsecase := NewUserUsecase(&TestUserRepository{}, &TestDBRepository{})
input := &model.User{ input := &model.User{
Email: "a@b.c", Email: "a@b.c",
FirstName: "James", FirstName: "James",
@ -51,7 +50,7 @@ func TestCreateUser(t *testing.T) {
t.Run("duplicate create", func(t *testing.T) { t.Run("duplicate create", func(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userUsecase := NewUserUsecase(&repomock.TestUserRepository{}, &repomock.TestDBRepository{}) userUsecase := NewUserUsecase(&TestUserRepository{}, &TestDBRepository{})
input := &model.User{ input := &model.User{
Email: "duplicate@error.com", Email: "duplicate@error.com",
FirstName: "James", FirstName: "James",
@ -69,30 +68,32 @@ func TestUserExist(t *testing.T) {
Name string Name string
User *model.User User *model.User
ExpErr error ExpErr error
ExpRes bool
}{ }{
{"user exists", &model.User{ {"user exists", &model.User{
Email: "a@b.c", Email: "a@b.c",
Password: "strongHashed", Password: "strongHashed",
}, nil}, }, nil, true},
{"query error", &model.User{ {"query error", &model.User{
Email: "query@error.com", Email: "query@error.com",
Password: "strongHashed", Password: "strongHashed",
}, repomock.UserTestDummyErr}, }, UserTestDummyErr, false},
{"user doesn not exist", &model.User{ {"user doesn not exist", &model.User{
Email: "inexist@error.com", Email: "inexist@error.com",
Password: "strongHashed", Password: "strongHashed",
}, UserNotExist}, }, UserNotExist, false},
{"wrong password", &model.User{ {"wrong password", &model.User{
Email: "a@b.c", Email: "a@b.c",
Password: "wrongHashed", Password: "wrongHashed",
}, UserWrongPassword}, }, UserWrongPassword, false},
} }
for _, tst := range testCases { for _, tst := range testCases {
ctx := context.Background() ctx := context.Background()
userUsecase := NewUserUsecase(&repomock.TestUserRepository{}, &repomock.TestDBRepository{}) userUsecase := NewUserUsecase(&TestUserRepository{}, &TestDBRepository{})
err := userUsecase.Exist(ctx, tst.User) got, err := userUsecase.Exist(ctx, tst.User)
assert.ErrorIs(t, err, tst.ExpErr) assert.ErrorIs(t, err, tst.ExpErr)
assert.Equal(t, tst.ExpRes, got)
} }
} }

View File

@ -1,25 +1,3 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package test package test
import ( import (