feat: Impl event while refactoring user domain
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build and test / Build (push) Successful in 2m21s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build and test / Build (push) Successful in 2m21s
				
			This commit is contained in:
		
							
								
								
									
										33
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								README.md
									
									
									
									
									
								
							@ -376,3 +376,36 @@ functionalities well decoupled and interfaces well defined.
 | 
			
		||||
 | 
			
		||||
I will add some tests for existing code and then it's time to move on to
 | 
			
		||||
my core business logic.
 | 
			
		||||
 | 
			
		||||
### 2024/10/16
 | 
			
		||||
 | 
			
		||||
I am facing a design problem. My way to implement the business logic is to
 | 
			
		||||
first write the core logic code in the domain service level. It will help me
 | 
			
		||||
to identify if there are any missing part in my model design. Thus, when
 | 
			
		||||
some of the business logic is done, I can create database migrations and then
 | 
			
		||||
implement the adapter level's code.
 | 
			
		||||
 | 
			
		||||
The problem is that my design depends heavily on the database. Taking the
 | 
			
		||||
example of adding an expense to en event.
 | 
			
		||||
 | 
			
		||||
Input is a valid `ExpenseDTO` which has the `event`, `paiements` and
 | 
			
		||||
`receptions`. What I must do is to open a database transaction where I:
 | 
			
		||||
 | 
			
		||||
1. Get the Event. (Most importantly the `TotalAmount`)
 | 
			
		||||
2. For each `paiemnt` and `reception` create a transaction related to the
 | 
			
		||||
`User`. And insert them into the database.
 | 
			
		||||
3. Update the `TotalAmount`
 | 
			
		||||
4. Update the caches if any
 | 
			
		||||
 | 
			
		||||
If any step fails, the transaction rolls back.
 | 
			
		||||
 | 
			
		||||
This has barely no logic at all. I think it is not suitable to try to tie
 | 
			
		||||
this operation to the domain model.
 | 
			
		||||
 | 
			
		||||
However, there is something that worth a domain model level method, that
 | 
			
		||||
is to calculate the share of each members of the event, where we will have
 | 
			
		||||
the list of members and the amount of balance they have. And then we will
 | 
			
		||||
do the calculate and send back a list of money one should pay for another.
 | 
			
		||||
 | 
			
		||||
Finally, I think the business logic is still too simple to be put into a
 | 
			
		||||
"Domain". For now, the service layer is just enough.
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,10 @@ func NewtestUserUsecase() usecase.User {
 | 
			
		||||
	return &testUserUsecase{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*testUserUsecase) Create(ctx context.Context, u *model.UserCreateDTO) (*model.User, error) {
 | 
			
		||||
func (*testUserUsecase) Create(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	u *model.UserCreateDTO,
 | 
			
		||||
) (*model.UserInfoVO, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -53,3 +56,11 @@ func (*testUserUsecase) Exist(ctx context.Context, u *model.UserExistDTO) error
 | 
			
		||||
	// Should never reach here
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*testUserUsecase) GetUserBaseVOByID(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	userID int,
 | 
			
		||||
) (*model.UserBaseVO, error) {
 | 
			
		||||
	// TODO:
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -30,3 +30,8 @@ RETURNING *;
 | 
			
		||||
SELECT id, email, first_name, last_name, password, created_at, updated_at
 | 
			
		||||
    FROM "user"
 | 
			
		||||
    WHERE email = $1;
 | 
			
		||||
 | 
			
		||||
-- name: GetUserByID :one
 | 
			
		||||
SELECT id, email, first_name, last_name, password, created_at, updated_at
 | 
			
		||||
    FROM "user"
 | 
			
		||||
    WHERE id = $1;
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,27 @@ func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getUserByID = `-- name: GetUserByID :one
 | 
			
		||||
SELECT id, email, first_name, last_name, password, created_at, updated_at
 | 
			
		||||
    FROM "user"
 | 
			
		||||
    WHERE id = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetUserByID(ctx context.Context, id int32) (User, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, getUserByID, id)
 | 
			
		||||
	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" (
 | 
			
		||||
 | 
			
		||||
@ -108,3 +108,24 @@ func (ur *userRepository) GetByEmail(ctx context.Context, email string) (*model.
 | 
			
		||||
		UpdatedAt: userDB.CreatedAt,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ur *userRepository) GetByID(ctx context.Context, id int) (*model.UserPO, error) {
 | 
			
		||||
	queries := sqlc.New(ur.db)
 | 
			
		||||
	userDB, err := queries.GetUserByID(ctx, int32(id))
 | 
			
		||||
	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.UserPO{
 | 
			
		||||
		ID:        int(userDB.ID),
 | 
			
		||||
		Email:     userDB.Email,
 | 
			
		||||
		FirstName: userDB.FirstName,
 | 
			
		||||
		LastName:  userDB.LastName,
 | 
			
		||||
		Password:  userDB.Password,
 | 
			
		||||
		CreatedAt: userDB.CreatedAt,
 | 
			
		||||
		UpdatedAt: userDB.CreatedAt,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,12 +24,37 @@ package model
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
// {{{ DTO Data Transfer Object (from controller to service)
 | 
			
		||||
 | 
			
		||||
type EventCreateDTO struct {
 | 
			
		||||
	Name        string `json:"name"        binding:"requiered"`
 | 
			
		||||
	Description string `json:"description"`
 | 
			
		||||
	OwnerID     int    `json:"owner_id"    binding:"requiered,number"`
 | 
			
		||||
	Name            string   `json:"name"        binding:"requiered"`
 | 
			
		||||
	Description     string   `json:"description"`
 | 
			
		||||
	OwnerID         int      `json:"owner_id"    binding:"requiered,number"`
 | 
			
		||||
	DefaultCurrency Currency `json:"currency"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ VO View Object (from service to controller)
 | 
			
		||||
 | 
			
		||||
type EventInfoVO struct {
 | 
			
		||||
	ID int
 | 
			
		||||
 | 
			
		||||
	Name        string
 | 
			
		||||
	Description string
 | 
			
		||||
 | 
			
		||||
	TotalAmount Money
 | 
			
		||||
 | 
			
		||||
	Owner *UserBaseVO
 | 
			
		||||
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
	UpdatedAt time.Time
 | 
			
		||||
 | 
			
		||||
	Users []*UserBaseVO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ PO Persistant Object (Between the service and the repository)
 | 
			
		||||
 | 
			
		||||
type EventPO struct {
 | 
			
		||||
	ID int
 | 
			
		||||
 | 
			
		||||
@ -43,21 +68,26 @@ type EventPO struct {
 | 
			
		||||
	UpdatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Event struct {
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ DO Domain Object (Contains the domain service)
 | 
			
		||||
 | 
			
		||||
type EventDO struct {
 | 
			
		||||
	ID int
 | 
			
		||||
 | 
			
		||||
	Name        string
 | 
			
		||||
	Description string
 | 
			
		||||
 | 
			
		||||
	// lazy get using participation join
 | 
			
		||||
	Users []*User
 | 
			
		||||
	Users []*UserDO
 | 
			
		||||
	// lazy get
 | 
			
		||||
	Expenses []*Expense
 | 
			
		||||
 | 
			
		||||
	TotalAmount     Money
 | 
			
		||||
	DefaultCurrency Currency
 | 
			
		||||
	Owner           User
 | 
			
		||||
	Owner           *UserDO
 | 
			
		||||
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
	UpdatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,8 @@ package model
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
// {{{ DTO Data Transfer Object (from controller to service)
 | 
			
		||||
 | 
			
		||||
type UserCreateDTO struct {
 | 
			
		||||
	Email     string `json:"email"      binding:"required,email"`
 | 
			
		||||
	FirstName string `json:"first_name" binding:"required"`
 | 
			
		||||
@ -36,6 +38,29 @@ type UserExistDTO struct {
 | 
			
		||||
	Password string `json:"password" binding:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ VO View Object (from service to controller)
 | 
			
		||||
 | 
			
		||||
type UserBaseVO struct {
 | 
			
		||||
	ID        int    `json:"id"`
 | 
			
		||||
	FirstName string `json:"first_name"`
 | 
			
		||||
	LastName  string `json:"last_name"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UserInfoVO struct {
 | 
			
		||||
	// UserBaseVO
 | 
			
		||||
	ID        int    `json:"id"`
 | 
			
		||||
	FirstName string `json:"first_name"`
 | 
			
		||||
	LastName  string `json:"last_name"`
 | 
			
		||||
 | 
			
		||||
	Email     string `json:"email"`
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
	UpdatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ PO Persistant Object (Between the service and the repository)
 | 
			
		||||
 | 
			
		||||
type UserPO struct {
 | 
			
		||||
	ID int
 | 
			
		||||
 | 
			
		||||
@ -48,8 +73,11 @@ type UserPO struct {
 | 
			
		||||
	UpdatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// User model
 | 
			
		||||
type User struct {
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ DO Domain Object (Contains the domain service)
 | 
			
		||||
 | 
			
		||||
// TODO: For now I don't know what to do with this model
 | 
			
		||||
type UserDO struct {
 | 
			
		||||
	ID int
 | 
			
		||||
 | 
			
		||||
	Email     string
 | 
			
		||||
@ -63,3 +91,5 @@ type User struct {
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
	UpdatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								internal/howmuch/usecase/repo/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								internal/howmuch/usecase/repo/event.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
// 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 repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type EventRepository interface {
 | 
			
		||||
	Create(ctx context.Context, evPO *model.EventPO) (*model.EventPO, error)
 | 
			
		||||
 | 
			
		||||
	// UpdateInfo updates the event related information (name, descriptions)
 | 
			
		||||
	UpdateInfo()
 | 
			
		||||
 | 
			
		||||
	Delete() // XXX: Pay attention to the foreign key relationships
 | 
			
		||||
 | 
			
		||||
	GetByID()
 | 
			
		||||
 | 
			
		||||
	ListExpensesByUserID()
 | 
			
		||||
 | 
			
		||||
	// related to events of a user
 | 
			
		||||
	ListEventsByUserID()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExpenseRepository interface {
 | 
			
		||||
	Create()
 | 
			
		||||
	Update()
 | 
			
		||||
	Delete() // Delete also the related transactions
 | 
			
		||||
 | 
			
		||||
	GetByID()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ParticipationRepository interface {
 | 
			
		||||
	Create()
 | 
			
		||||
	Delete()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TransactionRepository interface {
 | 
			
		||||
	Create()
 | 
			
		||||
	// Delete() might be handled in the Expense
 | 
			
		||||
	// Transaction is a joined entity, we don't provide diret read operation
 | 
			
		||||
}
 | 
			
		||||
@ -31,4 +31,5 @@ import (
 | 
			
		||||
type UserRepository interface {
 | 
			
		||||
	Create(ctx context.Context, transaction interface{}, u *model.UserPO) (*model.UserPO, error)
 | 
			
		||||
	GetByEmail(ctx context.Context, email string) (*model.UserPO, error)
 | 
			
		||||
	GetByID(ctx context.Context, id int) (*model.UserPO, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										112
									
								
								internal/howmuch/usecase/usecase/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								internal/howmuch/usecase/usecase/event.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
			
		||||
// 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 usecase
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
			
		||||
	"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo"
 | 
			
		||||
	"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type eventUsecase struct {
 | 
			
		||||
	userUC            User
 | 
			
		||||
	eventRepo         repo.EventRepository
 | 
			
		||||
	expenseRepo       repo.ExpenseRepository
 | 
			
		||||
	participationRepo repo.ParticipationRepository
 | 
			
		||||
	transactionRepo   repo.TransactionRepository
 | 
			
		||||
 | 
			
		||||
	dbRepo repo.DBRepository
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// For the controller
 | 
			
		||||
type Event interface{}
 | 
			
		||||
 | 
			
		||||
func NewEventUsecase(
 | 
			
		||||
	uuc User,
 | 
			
		||||
	ev repo.EventRepository,
 | 
			
		||||
	ex repo.ExpenseRepository,
 | 
			
		||||
	pa repo.ParticipationRepository,
 | 
			
		||||
	tr repo.TransactionRepository,
 | 
			
		||||
	db repo.DBRepository,
 | 
			
		||||
) Event {
 | 
			
		||||
	return &eventUsecase{uuc, ev, ex, pa, tr, db}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (evuc *eventUsecase) CreateEvent(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	evDTO *model.EventCreateDTO,
 | 
			
		||||
) (*model.EventInfoVO, error) {
 | 
			
		||||
	// transfer evDTO to PO
 | 
			
		||||
 | 
			
		||||
	evPO := &model.EventPO{
 | 
			
		||||
		Name:            evDTO.Name,
 | 
			
		||||
		Description:     evDTO.Description,
 | 
			
		||||
		OwnerID:         evDTO.OwnerID,
 | 
			
		||||
		TotalAmount:     0,
 | 
			
		||||
		DefaultCurrency: string(evDTO.DefaultCurrency),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := evuc.dbRepo.Transaction(
 | 
			
		||||
		ctx,
 | 
			
		||||
		func(txCtx context.Context, tx interface{}) (interface{}, error) {
 | 
			
		||||
			created, err := evuc.eventRepo.Create(ctx, evPO)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// TODO: App log, maybe can be sent to some third party service.
 | 
			
		||||
			log.InfoLog(
 | 
			
		||||
				"created new event",
 | 
			
		||||
				"name",
 | 
			
		||||
				created.Name,
 | 
			
		||||
				"owner",
 | 
			
		||||
				created.OwnerID,
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			ownerVO, err := evuc.userUC.GetUserBaseVOByID(ctx, created.OwnerID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			evVO := &model.EventInfoVO{
 | 
			
		||||
				ID:          created.ID,
 | 
			
		||||
				Name:        created.Name,
 | 
			
		||||
				Description: created.Description,
 | 
			
		||||
				TotalAmount: model.MakeMoney(
 | 
			
		||||
					created.TotalAmount,
 | 
			
		||||
					model.Currency(created.DefaultCurrency),
 | 
			
		||||
				),
 | 
			
		||||
				Owner:     ownerVO,
 | 
			
		||||
				CreatedAt: created.CreatedAt,
 | 
			
		||||
			}
 | 
			
		||||
			return evVO, err
 | 
			
		||||
		})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res := data.(*model.EventInfoVO)
 | 
			
		||||
 | 
			
		||||
	return res, err
 | 
			
		||||
}
 | 
			
		||||
@ -50,7 +50,10 @@ func (tur *TestUserRepository) Create(
 | 
			
		||||
	return &user, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ur *TestUserRepository) GetByEmail(ctx context.Context, email string) (*model.UserPO, error) {
 | 
			
		||||
func (tur *TestUserRepository) GetByEmail(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	email string,
 | 
			
		||||
) (*model.UserPO, error) {
 | 
			
		||||
	hashedPwd, _ := bcrypt.GenerateFromPassword([]byte("strongHashed"), 12)
 | 
			
		||||
	switch email {
 | 
			
		||||
	case "a@b.c":
 | 
			
		||||
@ -67,3 +70,20 @@ func (ur *TestUserRepository) GetByEmail(ctx context.Context, email string) (*mo
 | 
			
		||||
 | 
			
		||||
	return nil, UserTestDummyErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tur *TestUserRepository) GetByID(ctx context.Context, id int) (*model.UserPO, error) {
 | 
			
		||||
	hashedPwd, _ := bcrypt.GenerateFromPassword([]byte("strongHashed"), 12)
 | 
			
		||||
	switch id {
 | 
			
		||||
	case 123:
 | 
			
		||||
		return &model.UserPO{
 | 
			
		||||
			ID:       123,
 | 
			
		||||
			Email:    "a@b.c",
 | 
			
		||||
			Password: string(hashedPwd),
 | 
			
		||||
		}, nil
 | 
			
		||||
	case 456:
 | 
			
		||||
		return nil, UserTestDummyErr
 | 
			
		||||
	case 789:
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, UserTestDummyErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -60,8 +60,9 @@ type userUsecase struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type User interface {
 | 
			
		||||
	Create(ctx context.Context, u *model.UserCreateDTO) (*model.User, error)
 | 
			
		||||
	Create(ctx context.Context, u *model.UserCreateDTO) (*model.UserInfoVO, error)
 | 
			
		||||
	Exist(ctx context.Context, u *model.UserExistDTO) error
 | 
			
		||||
	GetUserBaseVOByID(ctx context.Context, userID int) (*model.UserBaseVO, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewUserUsecase(r repo.UserRepository, d repo.DBRepository) User {
 | 
			
		||||
@ -71,7 +72,10 @@ func NewUserUsecase(r repo.UserRepository, d repo.DBRepository) User {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (uuc *userUsecase) Create(ctx context.Context, u *model.UserCreateDTO) (*model.User, error) {
 | 
			
		||||
func (uuc *userUsecase) Create(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	u *model.UserCreateDTO,
 | 
			
		||||
) (*model.UserInfoVO, error) {
 | 
			
		||||
	// Hash the password
 | 
			
		||||
	encrypted, err := bcrypt.GenerateFromPassword([]byte(u.Password), 12)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -100,9 +104,9 @@ func (uuc *userUsecase) Create(ctx context.Context, u *model.UserCreateDTO) (*mo
 | 
			
		||||
			log.InfoLog(
 | 
			
		||||
				"created new user",
 | 
			
		||||
				"email",
 | 
			
		||||
				u.Email,
 | 
			
		||||
				created.Email,
 | 
			
		||||
				"name",
 | 
			
		||||
				fmt.Sprintf("%s %s", u.FirstName, u.LastName),
 | 
			
		||||
				fmt.Sprintf("%s %s", created.FirstName, created.LastName),
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			return created, err
 | 
			
		||||
@ -115,15 +119,11 @@ func (uuc *userUsecase) Create(ctx context.Context, u *model.UserCreateDTO) (*mo
 | 
			
		||||
 | 
			
		||||
	userPO := data.(*model.UserPO)
 | 
			
		||||
 | 
			
		||||
	user := &model.User{
 | 
			
		||||
	user := &model.UserInfoVO{
 | 
			
		||||
		ID:        userPO.ID,
 | 
			
		||||
		Email:     userPO.Email,
 | 
			
		||||
		Password:  userPO.Password,
 | 
			
		||||
		FirstName: userPO.FirstName,
 | 
			
		||||
		LastName:  userPO.LastName,
 | 
			
		||||
 | 
			
		||||
		EventIDs: []int{}, // Unfilled
 | 
			
		||||
 | 
			
		||||
		CreatedAt: userPO.CreatedAt,
 | 
			
		||||
		UpdatedAt: userPO.UpdatedAt,
 | 
			
		||||
	}
 | 
			
		||||
@ -151,3 +151,19 @@ func (uuc *userUsecase) Exist(ctx context.Context, u *model.UserExistDTO) error
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (uuc *userUsecase) GetUserBaseVOByID(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	userID int,
 | 
			
		||||
) (*model.UserBaseVO, error) {
 | 
			
		||||
	got, err := uuc.userRepo.GetByID(ctx, userID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	userBaseVo := &model.UserBaseVO{
 | 
			
		||||
		ID:        got.ID,
 | 
			
		||||
		FirstName: got.FirstName,
 | 
			
		||||
		LastName:  got.LastName,
 | 
			
		||||
	}
 | 
			
		||||
	return userBaseVo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ import (
 | 
			
		||||
	"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
			
		||||
	"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase/repomock"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"golang.org/x/crypto/bcrypt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCreateUser(t *testing.T) {
 | 
			
		||||
@ -42,23 +41,13 @@ func TestCreateUser(t *testing.T) {
 | 
			
		||||
			LastName:  "Bond",
 | 
			
		||||
			Password:  "verystrong",
 | 
			
		||||
		}
 | 
			
		||||
		want := &model.User{
 | 
			
		||||
			ID:        123,
 | 
			
		||||
			Email:     input.Email,
 | 
			
		||||
			FirstName: input.FirstName,
 | 
			
		||||
			LastName:  input.LastName,
 | 
			
		||||
			// Password is hashed
 | 
			
		||||
			Password: "verystrong",
 | 
			
		||||
		want := &model.UserInfoVO{
 | 
			
		||||
			ID: 123,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		got, err := userUsecase.Create(ctx, input)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, want.ID, got.ID)
 | 
			
		||||
 | 
			
		||||
		assert.NoError(
 | 
			
		||||
			t,
 | 
			
		||||
			bcrypt.CompareHashAndPassword([]byte(got.Password), []byte(want.Password)),
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("duplicate create", func(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user