diff --git a/api/register.go b/api/register.go index 41769d8..0419d9a 100644 --- a/api/register.go +++ b/api/register.go @@ -1,6 +1,7 @@ package api import ( + "fmt" "net/http" "time" @@ -60,7 +61,7 @@ func (server *Server) RegisterUser(ctx *gin.Context) { return } - arg := db.InsertUserParams{ + argUser := db.InsertUserParams{ Username: req.Username, HashedPassword: hashedPassword, FullName: req.FullName, @@ -69,7 +70,17 @@ func (server *Server) RegisterUser(ctx *gin.Context) { Avatar: req.Avatar, BirthDate: req.BirthDate, } - user, err := server.store.InsertUser(ctx, arg) + argAfterCreate := func(user db.User) error { + fmt.Println("after create triggered") + return nil + } + + arg := db.RegisterUserTxParams{ + InsertUserParams: argUser, + AfterCreate: argAfterCreate, + } + + userAndProfile, err := server.store.RegisterUserTx(ctx, arg) if err != nil { @@ -94,21 +105,9 @@ func (server *Server) RegisterUser(ctx *gin.Context) { return } - responseUser := UserResponse{ - ID: user.ID, - Username: user.Username, - FullName: user.FullName, - Email: user.Email, - Role: user.Role, - Avatar: user.Avatar, - BirthDate: user.BirthDate, - PasswordChangedAt: user.PasswordChangedAt, - CreatedAt: user.CreatedAt, - } - BuildResponse(ctx, BaseResponse{ Code: http.StatusOK, - Data: responseUser, + Data: userAndProfile, Message: ResponseMessage{ Type: SUCCESS, Content: server.lm.Translate(localeValue, localization.User_RegisterSuccess), diff --git a/db/mock/store.go b/db/mock/store.go index 363fa3a..5a66cef 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -728,6 +728,21 @@ func (mr *MockStoreMockRecorder) ListOnboarding(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOnboarding", reflect.TypeOf((*MockStore)(nil).ListOnboarding), arg0) } +// RegisterUserTx mocks base method. +func (m *MockStore) RegisterUserTx(arg0 context.Context, arg1 db.RegisterUserTxParams) (db.RegisterUserTxResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterUserTx", arg0, arg1) + ret0, _ := ret[0].(db.RegisterUserTxResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RegisterUserTx indicates an expected call of RegisterUserTx. +func (mr *MockStoreMockRecorder) RegisterUserTx(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterUserTx", reflect.TypeOf((*MockStore)(nil).RegisterUserTx), arg0, arg1) +} + // UpdateCategory mocks base method. func (m *MockStore) UpdateCategory(arg0 context.Context, arg1 db.UpdateCategoryParams) (db.Category, error) { m.ctrl.T.Helper() diff --git a/db/sqlc/exec_tx.go b/db/sqlc/exec_tx.go new file mode 100644 index 0000000..791133e --- /dev/null +++ b/db/sqlc/exec_tx.go @@ -0,0 +1,25 @@ +package db + +import ( + "context" + "fmt" +) + +func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) error { + tx, err := store.connPool.Begin(ctx) + if err != nil { + return err + } + // * Note [codermuss]: Begin db transaction + q := New(tx) + // * Note [codermuss]: execute query + err = fn(q) + // * Note [codermuss]: if there is an error Rollback the query + if err != nil { + if rbErr := tx.Rollback(ctx); rbErr != nil { + return fmt.Errorf("tx err: %v, rb err: %v", err, rbErr) + } + return err + } + return tx.Commit(ctx) +} diff --git a/db/sqlc/store.go b/db/sqlc/store.go index fb02281..152813c 100644 --- a/db/sqlc/store.go +++ b/db/sqlc/store.go @@ -1,9 +1,14 @@ package db -import "github.com/jackc/pgx/v5/pgxpool" +import ( + "context" + + "github.com/jackc/pgx/v5/pgxpool" +) type Store interface { Querier + RegisterUserTx(ctx context.Context, arg RegisterUserTxParams) (RegisterUserTxResult, error) } type SQLStore struct { diff --git a/db/sqlc/tx_create_user.go b/db/sqlc/tx_create_user.go new file mode 100644 index 0000000..5559bc6 --- /dev/null +++ b/db/sqlc/tx_create_user.go @@ -0,0 +1,54 @@ +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +type RegisterUserTxParams struct { + InsertUserParams + // * Note [codermuss]: This function will be executed after the user is inserted, + // * Note [codermuss]: inside the same transaction. And its output error will be used to decide + // * Note [codermuss]: whether to commit or rollback the transaction. + AfterCreate func(user User) error +} +type RegisterUserTxResult struct { + User User + Profile Profile +} + +// * Note [codermuss]: This method responsible with creating user. +// * Note [codermuss]: It uses execTx to handle DB Transaction error +func (store *SQLStore) RegisterUserTx(ctx context.Context, arg RegisterUserTxParams) (RegisterUserTxResult, error) { + var result RegisterUserTxResult + + err := store.execTx(ctx, func(q *Queries) error { + var err error + result.User, err = q.InsertUser(ctx, arg.InsertUserParams) + if err != nil { + return err + } + profileArg := InsertProfileParams{ + UserID: result.User.ID, + Bio: pgtype.Text{Valid: true, String: ""}, + PostCount: pgtype.Int4{ + Valid: true, Int32: 0, + }, + LikeCount: pgtype.Int4{ + Valid: true, Int32: 0, + }, + FollowerCount: pgtype.Int4{ + Valid: true, Int32: 0, + }, + } + + result.Profile, err = q.InsertProfile(ctx, profileArg) + if err != nil { + return err + } + + return arg.AfterCreate(result.User) + }) + return result, err +}