From 7577c87f273c1a1589016d03aa74897cf5c75828 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Wed, 13 Aug 2025 14:26:14 +0700 Subject: [PATCH] feat (user): add create --- internal/domain/_template/single/dto.go | 4 +- internal/domain/main-entities/user/dto.go | 38 +++++++ internal/domain/main-entities/user/entity.go | 14 +++ .../use-case/_use-case-template/crud/case.go | 9 +- .../_use-case-template/crud/helper.go | 2 +- .../use-case/_use-case-template/crud/lib.go | 2 +- .../_use-case-template/crud/tycovar.go | 2 +- internal/use-case/main-use-case/user/case.go | 106 ++++++++++++++++++ .../use-case/main-use-case/user/helper.go | 24 ++++ internal/use-case/main-use-case/user/lib.go | 101 +++++++++++++++++ .../use-case/main-use-case/user/tycovar.go | 32 ++++++ pkg/password/password.go | 17 +++ 12 files changed, 342 insertions(+), 9 deletions(-) create mode 100644 internal/domain/main-entities/user/dto.go create mode 100644 internal/domain/main-entities/user/entity.go create mode 100644 internal/use-case/main-use-case/user/case.go create mode 100644 internal/use-case/main-use-case/user/helper.go create mode 100644 internal/use-case/main-use-case/user/lib.go create mode 100644 internal/use-case/main-use-case/user/tycovar.go create mode 100644 pkg/password/password.go diff --git a/internal/domain/_template/single/dto.go b/internal/domain/_template/single/dto.go index 472137a4..401ab571 100644 --- a/internal/domain/_template/single/dto.go +++ b/internal/domain/_template/single/dto.go @@ -1,6 +1,6 @@ package single -type Createdto struct { +type CreateDto struct { Code string `json:"code"` Name string `json:"name"` Description string `json:"description"` @@ -23,7 +23,7 @@ type ReadDetailDto struct { type Updatedto struct { Id uint `json:"id"` - Createdto + CreateDto } type Deletedto struct { diff --git a/internal/domain/main-entities/user/dto.go b/internal/domain/main-entities/user/dto.go new file mode 100644 index 00000000..b1335f1d --- /dev/null +++ b/internal/domain/main-entities/user/dto.go @@ -0,0 +1,38 @@ +package user + +import erc "simrs-vx/internal/domain/references/common" + +type CreateDto struct { + Name string `json:"name"` + Password string `json:"password"` + Status_Code erc.StatusCode `json:"status_code"` +} + +type ReadListDto struct { + Name string `json:"name"` + Status_Code erc.StatusCode `json:"status_code"` + + Page int `json:"page"` + PageSize int `json:"page_size"` + NoPagination int `json:"no_pagination"` +} + +type ReadDetailDto struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type Updatedto struct { + Id uint `json:"id"` + CreateDto +} + +type Deletedto struct { + Id uint `json:"id"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int64 `json:"count"` +} diff --git a/internal/domain/main-entities/user/entity.go b/internal/domain/main-entities/user/entity.go new file mode 100644 index 00000000..ac83a6b2 --- /dev/null +++ b/internal/domain/main-entities/user/entity.go @@ -0,0 +1,14 @@ +package user + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + erc "simrs-vx/internal/domain/references/common" +) + +type User struct { + ecore.Main // adjust this according to the needs + Name string `json:"name" gorm:"not null;size:25"` + Password string `json:"password" gorm:"not null;size:255"` + Status_Code erc.StatusCode `json:"status_code" gorm:"not null;size:10"` + FailedLoginCount uint `json:"failedLoginCount"` +} diff --git a/internal/use-case/_use-case-template/crud/case.go b/internal/use-case/_use-case-template/crud/case.go index 75adb080..89036fb7 100644 --- a/internal/use-case/_use-case-template/crud/case.go +++ b/internal/use-case/_use-case-template/crud/case.go @@ -23,7 +23,7 @@ const source = "crud" func Create(input e.Createdto) (*d.Data, error) { data := e.Single{} - setData(&data, &input) + setData(input, &data) event := pl.Event{ Feature: "Create", @@ -47,7 +47,7 @@ func Create(input e.Createdto) (*d.Data, error) { event.ErrInfo = pl.ErrorInfo{ Code: "MW_PRE_FAILED", // TODO: add to lang json Detail: fmt.Sprintf("Pre-middleware %s failed", mwName), - Raw: mwErr, + Raw: err, } return pl.SetLogError(event, data) } @@ -59,7 +59,8 @@ func Create(input e.Createdto) (*d.Data, error) { event.Action = "DBCreate" event.Status = "started" pl.SetLogInfo(event, data) - if result, err := CreateData(&data, tx).Error; err != nil { + result, err := CreateData(&data, tx) + if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-create-fail", @@ -83,7 +84,7 @@ func Create(input e.Createdto) (*d.Data, error) { event.ErrInfo = pl.ErrorInfo{ Code: "MW_POST_FAILED", // TODO: add to lang json Detail: fmt.Sprintf("Post-middleware %s failed", mwName), - Raw: mwErr, + Raw: err, } return pl.SetLogError(event, data) } diff --git a/internal/use-case/_use-case-template/crud/helper.go b/internal/use-case/_use-case-template/crud/helper.go index 0d09eb8f..c1f2e6aa 100644 --- a/internal/use-case/_use-case-template/crud/helper.go +++ b/internal/use-case/_use-case-template/crud/helper.go @@ -5,7 +5,7 @@ Any functions that are used internally by the use-case package crud import ( - e "simrs-vx/internal/domain/main-entities/single" + e "simrs-vx/internal/domain/_template/single" ) func setData(src e.Createdto, dst *e.Single) { diff --git a/internal/use-case/_use-case-template/crud/lib.go b/internal/use-case/_use-case-template/crud/lib.go index 24ee2398..aa26c48d 100644 --- a/internal/use-case/_use-case-template/crud/lib.go +++ b/internal/use-case/_use-case-template/crud/lib.go @@ -16,7 +16,7 @@ func CreateData(input *e.Single, dbx ...*gorm.DB) (*e.Single, error) { tx = dg.I } - if err := tx.Create(&data).Error; err != nil { + if err := tx.Create(&input).Error; err != nil { return nil, err } diff --git a/internal/use-case/_use-case-template/crud/tycovar.go b/internal/use-case/_use-case-template/crud/tycovar.go index 17945a31..0a5c27fd 100644 --- a/internal/use-case/_use-case-template/crud/tycovar.go +++ b/internal/use-case/_use-case-template/crud/tycovar.go @@ -14,7 +14,7 @@ import ( e "simrs-vx/internal/domain/main-entities/single" ) -type createMw func(input *e.Createdto, data *e.Single, tx *gorm.DB) error +type createMw func(input *e.CreateDto, data *e.Single, tx *gorm.DB) error type readListMw func(input *e.ReadListDto, data *e.Single, tx *gorm.DB) error type readDetailMw func(input *e.ReadDetailDto, data *e.Single, tx *gorm.DB) error type updateMw func(input *e.ReadDetailDto, data *e.Single, tx *gorm.DB) error diff --git a/internal/use-case/main-use-case/user/case.go b/internal/use-case/main-use-case/user/case.go new file mode 100644 index 00000000..779b344c --- /dev/null +++ b/internal/use-case/main-use-case/user/case.go @@ -0,0 +1,106 @@ +package user + +import ( + "fmt" + e "simrs-vx/internal/domain/main-entities/user" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + + pl "simrs-vx/pkg/logger" + + "gorm.io/gorm" +) + +const source = "user" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.User{} + + setData(input, &data) + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + event.Action = "Create" + event.Status = "started" + pl.SetLogInfo(event, input) + + err := dg.I.Transaction(func(tx *gorm.DB) error { + for i := range createPreMw { + mwName := fmt.Sprintf("createPreMw[%d]", i) + + event.Action = mwName + event.Status = "started" + pl.SetLogInfo(event, data) + + if err := createPreMw[i](&input, &data, tx); err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "MW_PRE_FAILED", // TODO: add to lang json + Detail: fmt.Sprintf("Pre-middleware %s failed", mwName), + Raw: err, + } + return pl.SetLogError(event, data) + } + + event.Status = "completed" + pl.SetLogInfo(event, nil) + } + + event.Action = "DBCreate" + event.Status = "started" + pl.SetLogInfo(event, data) + _, err := CreateData(&data, tx) + if err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-create-fail", + Detail: "Database insert failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + event.Status = "completed" + pl.SetLogInfo(event, nil) + + for i := range createPostMw { + mwName := fmt.Sprintf("createPostMw[%d]", i) + + event.Action = mwName + event.Status = "started" + pl.SetLogInfo(event, input) + if err := createPostMw[i](&input, &data, tx); err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "MW_POST_FAILED", // TODO: add to lang json + Detail: fmt.Sprintf("Post-middleware %s failed", mwName), + Raw: err, + } + return pl.SetLogError(event, data) + } + } + + event.Status = "completed" + pl.SetLogInfo(event, nil) + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "type": "list", + "status": "created", + }, + Data: data, + }, nil +} diff --git a/internal/use-case/main-use-case/user/helper.go b/internal/use-case/main-use-case/user/helper.go new file mode 100644 index 00000000..e16c080c --- /dev/null +++ b/internal/use-case/main-use-case/user/helper.go @@ -0,0 +1,24 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package user + +import ( + e "simrs-vx/internal/domain/main-entities/user" + + p "simrs-vx/pkg/password" +) + +func setData(src e.CreateDto, dst *e.User) error { + pass, err := p.Hash(src.Password) + if err != nil { + return err + } + + dst.Name = src.Name + dst.Password = pass + dst.Status_Code = src.Status_Code + + return nil +} diff --git a/internal/use-case/main-use-case/user/lib.go b/internal/use-case/main-use-case/user/lib.go new file mode 100644 index 00000000..27bbf221 --- /dev/null +++ b/internal/use-case/main-use-case/user/lib.go @@ -0,0 +1,101 @@ +package user + +import ( + e "simrs-vx/internal/domain/main-entities/user" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + "gorm.io/gorm" +) + +func CreateData(input *e.User, dbx ...*gorm.DB) (*e.User, error) { + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Create(&input).Error; err != nil { + return nil, err + } + + return input, nil +} + +func ReadListData(input e.ReadListDto, dbx ...*gorm.DB) ([]e.User, *e.MetaDto, error) { + data := []e.User{} + pagination := gh.Pagination{} + count := int64(0) + meta := e.MetaDto{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + tx = tx. + Model(&e.User{}). + // Joins("Patient"). // if needed + // Preload("Patient"). // if needed + Scopes(gh.Filter(input)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Order("CreatedAt DESC") + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, err + + } + meta.Count = count + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, dbx ...*gorm.DB) (*e.User, error) { + data := e.User{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.First(&data, input.Id).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + + return &data, nil +} + +func UpdateData(input e.User, dbx ...*gorm.DB) error { + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + return tx.Save(&input).Error +} + +func DeleteData(input *e.User, dbx ...*gorm.DB) error { + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + return tx.Delete(input).Error +} diff --git a/internal/use-case/main-use-case/user/tycovar.go b/internal/use-case/main-use-case/user/tycovar.go new file mode 100644 index 00000000..6169fba5 --- /dev/null +++ b/internal/use-case/main-use-case/user/tycovar.go @@ -0,0 +1,32 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package user + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/user" +) + +type createMw func(input *e.CreateDto, data *e.User, tx *gorm.DB) error +type readListMw func(input *e.ReadListDto, data *e.User, tx *gorm.DB) error +type readDetailMw func(input *e.ReadDetailDto, data *e.User, tx *gorm.DB) error +type updateMw func(input *e.ReadDetailDto, data *e.User, tx *gorm.DB) error +type deleteMw func(input *e.ReadDetailDto, data *e.User, tx *gorm.DB) error + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var udpatePreMw []readDetailMw +var udpatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw diff --git a/pkg/password/password.go b/pkg/password/password.go new file mode 100644 index 00000000..fa506338 --- /dev/null +++ b/pkg/password/password.go @@ -0,0 +1,17 @@ +package password + +import ( + "golang.org/x/crypto/bcrypt" +) + +var Cost = 10 // can be set by consumer + +func Hash(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), Cost) + return string(bytes), err +} + +func Check(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +}