diff --git a/Dockerfile.dev b/Dockerfile.dev index 007795d..46ebf8f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,11 +1,7 @@ -FROM golang:1.14-stretch - +FROM golang:1.21 WORKDIR /app - COPY . . - -RUN go mod download && go get github.com/cespare/reflex - -COPY /reflex.conf / - -ENTRYPOINT ["reflex", "-c", "./reflex.conf"] \ No newline at end of file +RUN go mod download +RUN go build -o /go-transactions +EXPOSE 3001 +CMD ["/go-transactions"] \ No newline at end of file diff --git a/Makefile b/Makefile index f23a3d0..e741198 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PWD = $(shell pwd -L) IMAGE_NAME = gsabadini/go-transactions -DOCKER_RUN = docker run --rm -it -w /app -v ${PWD}:/app golang:1.14-stretch +DOCKER_RUN = docker run --rm -it -w /app -v ${PWD}:/app golang:1.21-alpine start: init up diff --git a/_scripts/mysql/init.sql b/_scripts/mysql/init.sql index 5dc365b..df083ce 100644 --- a/_scripts/mysql/init.sql +++ b/_scripts/mysql/init.sql @@ -23,18 +23,6 @@ CREATE TABLE transactions ( FOREIGN KEY (operation_id) REFERENCES operations(id) ); -CREATE TABLE authorizations ( - id VARCHAR(36) PRIMARY KEY UNIQUE, - account_id VARCHAR(36) NOT NULL, - operation_id VARCHAR(36) NOT NULL, - amount INTEGER NOT NULL, - created_at TIMESTAMP, - - FOREIGN KEY (account_id) REFERENCES accounts(id), - FOREIGN KEY (operation_id) REFERENCES operations(id) -); - - INSERT INTO `operations` (`id`, `description`, `type`) diff --git a/adapter/api/handler/create_cancel.go b/adapter/api/handler/create_cancel.go deleted file mode 100644 index abeebd1..0000000 --- a/adapter/api/handler/create_cancel.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/adapter/api/handler/create_cashin.go b/adapter/api/handler/create_cashin.go deleted file mode 100644 index abeebd1..0000000 --- a/adapter/api/handler/create_cashin.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/adapter/api/handler/create_cashout.go b/adapter/api/handler/create_cashout.go deleted file mode 100644 index dfceb00..0000000 --- a/adapter/api/handler/create_cashout.go +++ /dev/null @@ -1,71 +0,0 @@ -package handler - -import ( - "encoding/json" - "log" - "net/http" - - "github.com/GSabadini/go-transactions/adapter/api/response" - "github.com/GSabadini/go-transactions/domain" - "github.com/GSabadini/go-transactions/infrastructure/validation" - "github.com/GSabadini/go-transactions/usecase" - - "github.com/go-playground/validator/v10" -) - -// CreateCashoutHandler defines the dependencies of the HTTP handler for the use case -type CreateCashoutHandler struct { - uc usecase.CreateAuthorizationUseCase - log *log.Logger - validator *validator.Validate -} - -// NewCreateCashoutHandler creates new CreateCashoutHandler with its dependencies -func NewCreateCashoutHandler( - uc usecase.CreateAuthorizationUseCase, - log *log.Logger, - v *validator.Validate, -) CreateCashoutHandler { - return CreateCashoutHandler{ - uc: uc, - log: log, - validator: v, - } -} - -// Handle exposes the http handler -func (c CreateCashoutHandler) Handle(w http.ResponseWriter, r *http.Request) { - var input usecase.CreateAuthorizationInput - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - c.log.Println("failed to marshal message:", err) - response.NewError([]string{err.Error()}, http.StatusBadRequest).Send(w) - return - } - defer r.Body.Close() - - if err := c.validator.Struct(input); err != nil { - errs := validation.ErrMessages(err) - c.log.Println("invalid input:", errs) - response.NewError(errs, http.StatusBadRequest).Send(w) - return - } - - output, err := c.uc.Execute(r.Context(), input) - if err != nil { - c.log.Println("failed to creating transaction:", err) - switch err { - case domain.ErrOperationInvalid: - response.NewError([]string{err.Error()}, http.StatusUnprocessableEntity).Send(w) - return - case domain.ErrAccountInsufficientCreditLimit: - response.NewError([]string{err.Error()}, http.StatusUnprocessableEntity).Send(w) - return - default: - response.NewError([]string{err.Error()}, http.StatusInternalServerError).Send(w) - return - } - } - - c.log.Println("success to creating cashout") - response.NewSuccess(output, http.StatusCreated).Send(w) -} diff --git a/adapter/api/handler/create_peer_too_peer.go b/adapter/api/handler/create_peer_too_peer.go deleted file mode 100644 index abeebd1..0000000 --- a/adapter/api/handler/create_peer_too_peer.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/adapter/presenter/create_cashout.go b/adapter/presenter/create_cashout.go deleted file mode 100644 index bb0d9d5..0000000 --- a/adapter/presenter/create_cashout.go +++ /dev/null @@ -1,30 +0,0 @@ -package presenter - -import ( - "time" - - "github.com/GSabadini/go-transactions/domain" - "github.com/GSabadini/go-transactions/usecase" -) - -type createCashoutPresenter struct{} - -// NewCreateCashoutPresenter creates new createCashoutPresenter -func NewCreateCashoutPresenter() usecase.CreateAuthorizationPresenter { - return createCashoutPresenter{} -} - -// Output returns the cashout creation response -func (c createCashoutPresenter) Output(authorization domain.Authorization) usecase.CreateAuthorizationOutput { - return usecase.CreateAuthorizationOutput{ - ID: authorization.ID(), - AccountID: authorization.AccountID(), - Operation: usecase.CreateAuthorizationOperationOutput{ - ID: authorization.Operation().ID(), - Description: authorization.Operation().Description(), - Type: authorization.Operation().Type(), - }, - Amount: authorization.Amount(), - CreatedAt: authorization.CreatedAt().Format(time.RFC3339), - } -} diff --git a/adapter/repository/create_authorization.go b/adapter/repository/create_authorization.go deleted file mode 100644 index 2a4dc68..0000000 --- a/adapter/repository/create_authorization.go +++ /dev/null @@ -1,65 +0,0 @@ -package repository - -import ( - "context" - "database/sql" - - "github.com/GSabadini/go-transactions/domain" - - "github.com/pkg/errors" -) - -type createAuthorizationRepository struct { - db *sql.DB -} - -// NewCreateAuthorizationRepository creates new createAuthorizationRepository with its dependencies -func NewCreateAuthorizationRepository(db *sql.DB) domain.AuthorizationCreator { - return createAuthorizationRepository{ - db: db, - } -} - -// Create performs insert into the database -func (c createAuthorizationRepository) Create(ctx context.Context, authorization domain.Authorization) (domain.Authorization, error) { - tx, ok := ctx.Value("TxKey").(*sql.Tx) - if !ok { - var err error - tx, err = c.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return domain.Authorization{}, errors.Wrap(err, errUnknown.Error()) - } - } - - if _, err := tx.ExecContext( - ctx, - `INSERT INTO authorizations (id, account_id, operation_id, amount, balance, created_at) VALUES (?, ?, ?, ?, ?, ?)`, - authorization.ID(), - authorization.AccountID(), - authorization.Operation().ID(), - authorization.Amount(), - authorization.CreatedAt(), - ); err != nil { - return domain.Authorization{}, errors.Wrap(err, errUnknown.Error()) - } - - return authorization, nil -} - -func (c createAuthorizationRepository) WithTransaction(ctx context.Context, fn func(ctxFn context.Context) error) error { - tx, err := c.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return errors.Wrap(err, errUnknown.Error()) - } - - ctxTx := context.WithValue(ctx, "TxKey", tx) - err = fn(ctxTx) - if err != nil { - if rbErr := tx.Rollback(); rbErr != nil { - return errors.Wrap(err, "rollback error") - } - return err - } - - return tx.Commit() -} diff --git a/docker-compose.yml b/docker-compose.yml index c7a5e1d..790e5ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,20 +1,20 @@ -version: "3.3" +version: "3.7" services: app: - container_name: "app" + container_name: "go-transactions" build: context: . dockerfile: Dockerfile.dev ports: - - 3001:3001 + - "3001:3001" volumes: - .:/app env_file: - .env mysql: - container_name: "mysql" + container_name: "mysql-transactions" image: mysql:5.7 env_file: - .env diff --git a/domain/authorization.go b/domain/authorization.go deleted file mode 100644 index e13e18a..0000000 --- a/domain/authorization.go +++ /dev/null @@ -1,52 +0,0 @@ -package domain - -import ( - "context" - "time" -) - -type ( - // AuthorizationCreator defines the operation of creating an authorization entity - AuthorizationCreator interface { - Create(context.Context, Authorization) (Authorization, error) - WithTransaction(context.Context, func(context.Context) error) error - } - - Authorization struct { - id string - accountID string - operation Operation - amount int64 - createdAt time.Time - } -) - -func NewAuthorization(id string, accID string, op Operation, amount int64, createdAt time.Time) Authorization { - return Authorization{ - id: id, - accountID: accID, - operation: op, - amount: amount, - createdAt: createdAt, - } -} - -func (a Authorization) ID() string { - return a.id -} - -func (a Authorization) Amount() int64 { - return a.amount -} - -func (a Authorization) AccountID() string { - return a.accountID -} - -func (a Authorization) Operation() Operation { - return a.operation -} - -func (a Authorization) CreatedAt() time.Time { - return a.createdAt -} diff --git a/go.mod b/go.mod index 795863b..02121a0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/GSabadini/go-transactions -go 1.14 +go 1.21 require ( github.com/go-playground/locales v0.13.0 @@ -11,3 +11,9 @@ require ( github.com/gorilla/mux v1.8.0 github.com/pkg/errors v0.9.1 ) + +require ( + github.com/leodido/go-urn v1.2.0 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect +) diff --git a/go.sum b/go.sum index 3d91dff..0c1c03a 100644 --- a/go.sum +++ b/go.sum @@ -33,7 +33,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/usecase/create_authorization.go b/usecase/create_authorization.go deleted file mode 100644 index f7808cc..0000000 --- a/usecase/create_authorization.go +++ /dev/null @@ -1,169 +0,0 @@ -package usecase - -import ( - "context" - "time" - - "github.com/GSabadini/go-transactions/domain" - - "github.com/google/uuid" -) - -type ( - Evaluator interface { - Rules(context.Context, string) error - } - - External interface { - Acquirer(context.Context, string) error - } - - Event interface { - Audit(context.Context, domain.Authorization) - Transactions(context.Context, domain.Authorization) - } - - // Input port - CreateAuthorizationUseCase interface { - Execute(context.Context, CreateAuthorizationInput) (CreateAuthorizationOutput, error) - } - - // Input data - CreateAuthorizationInput struct { - AccountID string `json:"account_id" validate:"required"` - OperationID string `json:"operation_id" validate:"required"` - Amount int64 `json:"amount" validate:"required,gt=0"` - External bool `json:"external" validate:"required,bool"` - } - - // Output port - CreateAuthorizationPresenter interface { - Output(domain.Authorization) CreateAuthorizationOutput - } - - // Output data - CreateAuthorizationOutput struct { - ID string `json:"id"` - AccountID string `json:"account_id"` - Operation CreateAuthorizationOperationOutput `json:"operation"` - Amount int64 `json:"amount"` - CreatedAt string `json:"created_at"` - } - - // Output data - CreateAuthorizationOperationOutput struct { - ID string `json:"id"` - Description string `json:"description"` - Type string `json:"type"` - } - - createAuthorizationInteractor struct { - repoAuthorizationCreator domain.AuthorizationCreator - repoAccountFinder domain.AccountFinder - repoAccountUpdater domain.AccountUpdater - evaluator Evaluator - external External - event Event - pre CreateAuthorizationPresenter - ctxTimeout time.Duration - } -) - -// NewCreateAuthorizationInteractor creates new createAuthorizationInteractor with its dependencies -func NewCreateAuthorizationInteractor( - repoAuthorizationCreator domain.AuthorizationCreator, - repoAccountFinder domain.AccountFinder, - repoAccountUpdater domain.AccountUpdater, - evaluator Evaluator, - external External, - event Event, - pre CreateAuthorizationPresenter, - ctxTimeout time.Duration, -) CreateAuthorizationUseCase { - return createAuthorizationInteractor{ - repoAuthorizationCreator: repoAuthorizationCreator, - repoAccountFinder: repoAccountFinder, - repoAccountUpdater: repoAccountUpdater, - evaluator: evaluator, - external: external, - event: event, - pre: pre, - ctxTimeout: ctxTimeout, - } -} - -// Execute orchestrates the use case -func (c createAuthorizationInteractor) Execute(ctx context.Context, i CreateAuthorizationInput) (CreateAuthorizationOutput, error) { - ctx, cancel := context.WithTimeout(ctx, c.ctxTimeout) - defer cancel() - - var ( - account domain.Account - authorization domain.Authorization - err error - ) - - op, err := domain.NewOperation(i.OperationID) - if err != nil { - return c.pre.Output(domain.Authorization{}), err - } - - err = c.repoAuthorizationCreator.WithTransaction(ctx, func(ctxTx context.Context) error { - account, err = c.repoAccountFinder.FindByID(ctxTx, i.AccountID) - if err != nil { - return err - } - - if err = c.evaluator.Rules(ctx, i.AccountID); err != nil { - return err - } - - if i.External { - if err = c.external.Acquirer(ctx, i.AccountID); err != nil { - return err - } - - authorization, err = c.repoAuthorizationCreator.Create(ctxTx, domain.NewAuthorization( - uuid.New().String(), - i.AccountID, - op, - i.Amount, - time.Now(), - )) - if err != nil { - return err - } - - return nil - } - - if err = account.PaymentOperation(i.Amount, op.Type()); err != nil { - return err - } - - if err = c.repoAccountUpdater.UpdateCreditLimit(ctxTx, account.ID(), account.AvailableCreditLimit()); err != nil { - return err - } - - authorization, err = c.repoAuthorizationCreator.Create(ctxTx, domain.NewAuthorization( - uuid.New().String(), - i.AccountID, - op, - i.Amount, - time.Now(), - )) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return c.pre.Output(domain.Authorization{}), err - } - - go c.event.Audit(ctx, authorization) - go c.event.Transactions(ctx, authorization) - - return c.pre.Output(authorization), nil -}