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:
parent
29633e0e95
commit
a7a915d825
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
|
I will add some tests for existing code and then it's time to move on to
|
||||||
my core business logic.
|
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{}
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,3 +56,11 @@ func (*testUserUsecase) Exist(ctx context.Context, u *model.UserExistDTO) error
|
|||||||
// Should never reach here
|
// Should never reach here
|
||||||
return nil
|
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
|
SELECT id, email, first_name, last_name, password, created_at, updated_at
|
||||||
FROM "user"
|
FROM "user"
|
||||||
WHERE email = $1;
|
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
|
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
|
const insertUser = `-- name: InsertUser :one
|
||||||
|
|
||||||
INSERT INTO "user" (
|
INSERT INTO "user" (
|
||||||
|
@ -108,3 +108,24 @@ func (ur *userRepository) GetByEmail(ctx context.Context, email string) (*model.
|
|||||||
UpdatedAt: userDB.CreatedAt,
|
UpdatedAt: userDB.CreatedAt,
|
||||||
}, nil
|
}, 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"
|
import "time"
|
||||||
|
|
||||||
|
// {{{ DTO Data Transfer Object (from controller to service)
|
||||||
|
|
||||||
type EventCreateDTO struct {
|
type EventCreateDTO struct {
|
||||||
Name string `json:"name" binding:"requiered"`
|
Name string `json:"name" binding:"requiered"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
OwnerID int `json:"owner_id" binding:"requiered,number"`
|
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 {
|
type EventPO struct {
|
||||||
ID int
|
ID int
|
||||||
|
|
||||||
@ -43,21 +68,26 @@ type EventPO struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
// }}}
|
||||||
|
// {{{ DO Domain Object (Contains the domain service)
|
||||||
|
|
||||||
|
type EventDO struct {
|
||||||
ID int
|
ID int
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
|
|
||||||
// lazy get using participation join
|
// lazy get using participation join
|
||||||
Users []*User
|
Users []*UserDO
|
||||||
// lazy get
|
// lazy get
|
||||||
Expenses []*Expense
|
Expenses []*Expense
|
||||||
|
|
||||||
TotalAmount Money
|
TotalAmount Money
|
||||||
DefaultCurrency Currency
|
DefaultCurrency Currency
|
||||||
Owner User
|
Owner *UserDO
|
||||||
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
@ -24,6 +24,8 @@ package model
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// {{{ DTO Data Transfer Object (from controller to service)
|
||||||
|
|
||||||
type UserCreateDTO struct {
|
type UserCreateDTO struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
FirstName string `json:"first_name" binding:"required"`
|
FirstName string `json:"first_name" binding:"required"`
|
||||||
@ -36,6 +38,29 @@ type UserExistDTO struct {
|
|||||||
Password string `json:"password" binding:"required"`
|
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 {
|
type UserPO struct {
|
||||||
ID int
|
ID int
|
||||||
|
|
||||||
@ -48,8 +73,11 @@ type UserPO struct {
|
|||||||
UpdatedAt time.Time
|
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
|
ID int
|
||||||
|
|
||||||
Email string
|
Email string
|
||||||
@ -63,3 +91,5 @@ type User struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt 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 {
|
type UserRepository interface {
|
||||||
Create(ctx context.Context, transaction interface{}, u *model.UserPO) (*model.UserPO, error)
|
Create(ctx context.Context, transaction interface{}, u *model.UserPO) (*model.UserPO, error)
|
||||||
GetByEmail(ctx context.Context, email string) (*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
|
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)
|
hashedPwd, _ := bcrypt.GenerateFromPassword([]byte("strongHashed"), 12)
|
||||||
switch email {
|
switch email {
|
||||||
case "a@b.c":
|
case "a@b.c":
|
||||||
@ -67,3 +70,20 @@ func (ur *TestUserRepository) GetByEmail(ctx context.Context, email string) (*mo
|
|||||||
|
|
||||||
return nil, UserTestDummyErr
|
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 {
|
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
|
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 {
|
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
|
// Hash the password
|
||||||
encrypted, err := bcrypt.GenerateFromPassword([]byte(u.Password), 12)
|
encrypted, err := bcrypt.GenerateFromPassword([]byte(u.Password), 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,9 +104,9 @@ func (uuc *userUsecase) Create(ctx context.Context, u *model.UserCreateDTO) (*mo
|
|||||||
log.InfoLog(
|
log.InfoLog(
|
||||||
"created new user",
|
"created new user",
|
||||||
"email",
|
"email",
|
||||||
u.Email,
|
created.Email,
|
||||||
"name",
|
"name",
|
||||||
fmt.Sprintf("%s %s", u.FirstName, u.LastName),
|
fmt.Sprintf("%s %s", created.FirstName, created.LastName),
|
||||||
)
|
)
|
||||||
|
|
||||||
return created, err
|
return created, err
|
||||||
@ -115,15 +119,11 @@ func (uuc *userUsecase) Create(ctx context.Context, u *model.UserCreateDTO) (*mo
|
|||||||
|
|
||||||
userPO := data.(*model.UserPO)
|
userPO := data.(*model.UserPO)
|
||||||
|
|
||||||
user := &model.User{
|
user := &model.UserInfoVO{
|
||||||
ID: userPO.ID,
|
ID: userPO.ID,
|
||||||
Email: userPO.Email,
|
Email: userPO.Email,
|
||||||
Password: userPO.Password,
|
|
||||||
FirstName: userPO.FirstName,
|
FirstName: userPO.FirstName,
|
||||||
LastName: userPO.LastName,
|
LastName: userPO.LastName,
|
||||||
|
|
||||||
EventIDs: []int{}, // Unfilled
|
|
||||||
|
|
||||||
CreatedAt: userPO.CreatedAt,
|
CreatedAt: userPO.CreatedAt,
|
||||||
UpdatedAt: userPO.UpdatedAt,
|
UpdatedAt: userPO.UpdatedAt,
|
||||||
}
|
}
|
||||||
@ -151,3 +151,19 @@ func (uuc *userUsecase) Exist(ctx context.Context, u *model.UserExistDTO) error
|
|||||||
|
|
||||||
return nil
|
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/model"
|
||||||
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase/repomock"
|
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase/repomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateUser(t *testing.T) {
|
func TestCreateUser(t *testing.T) {
|
||||||
@ -42,23 +41,13 @@ func TestCreateUser(t *testing.T) {
|
|||||||
LastName: "Bond",
|
LastName: "Bond",
|
||||||
Password: "verystrong",
|
Password: "verystrong",
|
||||||
}
|
}
|
||||||
want := &model.User{
|
want := &model.UserInfoVO{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
Email: input.Email,
|
|
||||||
FirstName: input.FirstName,
|
|
||||||
LastName: input.LastName,
|
|
||||||
// Password is hashed
|
|
||||||
Password: "verystrong",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := userUsecase.Create(ctx, input)
|
got, err := userUsecase.Create(ctx, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want.ID, got.ID)
|
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) {
|
t.Run("duplicate create", func(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user