Skip to content

Commit

Permalink
Merge pull request #30 from bruce-mig/ft/create-transfer
Browse files Browse the repository at this point in the history
Ft/create transfer
  • Loading branch information
bruce-mig committed Mar 4, 2024
2 parents d2a7977 + 701d448 commit fcf3b8b
Show file tree
Hide file tree
Showing 16 changed files with 1,684 additions and 138 deletions.
2 changes: 1 addition & 1 deletion doc/statik/statik.go

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions doc/swagger/simple_bank.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,40 @@
]
}
},
"/v1/transfers": {
"post": {
"summary": "Transfer an amount",
"description": "Use this API to create a transfer",
"operationId": "SimpleBank_CreateTransfer",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/pbCreateTransferResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/pbCreateTransferRequest"
}
}
],
"tags": [
"SimpleBank"
]
}
},
"/v1/update_user": {
"patch": {
"summary": "Update an existing user",
Expand Down Expand Up @@ -427,6 +461,46 @@
}
}
},
"pbCreateTransferRequest": {
"type": "object",
"properties": {
"fromAccountId": {
"type": "string",
"format": "int64"
},
"toAccountId": {
"type": "string",
"format": "int64"
},
"amount": {
"type": "string",
"format": "int64"
},
"currency": {
"type": "string"
}
}
},
"pbCreateTransferResponse": {
"type": "object",
"properties": {
"transfer": {
"$ref": "#/definitions/pbTransfer"
},
"fromAccount": {
"$ref": "#/definitions/pbAccount"
},
"toAccount": {
"$ref": "#/definitions/pbAccount"
},
"fromEntry": {
"$ref": "#/definitions/pbEntry"
},
"toEntry": {
"$ref": "#/definitions/pbEntry"
}
}
},
"pbCreateUserRequest": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -460,6 +534,27 @@
}
}
},
"pbEntry": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "int64"
},
"accountId": {
"type": "string",
"format": "int64"
},
"amount": {
"type": "string",
"format": "int64"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"pbGetAccountResponse": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -536,6 +631,31 @@
}
}
},
"pbTransfer": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "int64"
},
"fromAccountId": {
"type": "string",
"format": "int64"
},
"toAccountId": {
"type": "string",
"format": "int64"
},
"amount": {
"type": "string",
"format": "int64"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"pbUpdateAccountResponse": {
"type": "object",
"properties": {
Expand Down
29 changes: 29 additions & 0 deletions gapi/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,32 @@ func convertAccounts(accounts []db.Account) []*pb.Account {

return accArr
}

func convertTransfer(transfer db.Transfer) *pb.Transfer {
return &pb.Transfer{
Id: transfer.ID,
FromAccountId: transfer.FromAccountID,
ToAccountId: transfer.ToAccountID,
Amount: transfer.Amount,
CreatedAt: timestamppb.New(transfer.CreatedAt),
}
}

func convertEntry(entry db.Entry) *pb.Entry {
return &pb.Entry{
Id: entry.ID,
AccountId: entry.AccountID,
Amount: entry.Amount,
CreatedAt: timestamppb.New(entry.CreatedAt),
}
}

func convertTranferTxResponse(result db.TransferTxResult) *pb.CreateTransferResponse {
return &pb.CreateTransferResponse{
Transfer: convertTransfer(result.Transfer),
FromAccount: convertAccount(result.FromAccount),
ToAccount: convertAccount(result.ToAccount),
FromEntry: convertEntry(result.FromEntry),
ToEntry: convertEntry(result.ToEntry),
}
}
112 changes: 112 additions & 0 deletions gapi/rpc_create_transfer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package gapi

import (
"context"
"errors"
"fmt"

db "github.com/bruce-mig/simple-bank/db/sqlc"
"github.com/bruce-mig/simple-bank/pb"
"github.com/bruce-mig/simple-bank/util"
"github.com/bruce-mig/simple-bank/val"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (server *Server) CreateTransfer(ctx context.Context, req *pb.CreateTransferRequest) (*pb.CreateTransferResponse, error) {
authPayload, err := server.authorizeUser(ctx, []string{util.BankerRole, util.DepositorRole})
if err != nil {
return nil, unauthenticatedError(err)
}

violations := ValidateCreateTransferRequest(req)
if violations != nil {
return nil, invalidArgumentError(violations)
}

fromAccount, valid, _ := server.validAccount(ctx, req.GetFromAccountId(), req.GetCurrency())

if !valid {
return nil, status.Errorf(
codes.Internal,
"account validation failed",
)
}

if authPayload.Role != util.BankerRole && fromAccount.Owner != authPayload.Username {
return nil, status.Errorf(
codes.PermissionDenied,
"from account doesn't belong to the authenticated user",
)
}
_, valid, _ = server.validAccount(ctx, req.GetToAccountId(), req.GetCurrency())
if !valid {
return nil, status.Errorf(
codes.Internal,
"account validation failed",
)
}

arg := db.TransferTxParams{
FromAccountID: req.GetFromAccountId(),
ToAccountID: req.GetToAccountId(),
Amount: req.GetAmount(),
}
result, err := server.store.TransferTx(ctx, arg)
if err != nil {
return nil, status.Errorf(
codes.Internal,
"failed to execute transfer",
)
}

res := convertTranferTxResponse(result)
return res, nil
}

func (server *Server) validAccount(ctx context.Context, accountID int64, currency string) (db.Account, bool, error) {
account, err := server.store.GetAccount(ctx, accountID)
if err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return account, false, status.Errorf(
codes.NotFound,
"account doesn't exist",
)
}
return account, false, status.Errorf(
codes.Internal,
"failed to get account",
)
}

if account.Currency != currency {
return account, false, status.Errorf(
codes.Internal,
fmt.Sprintf("account [%d] currency mismatch: %s vs %s", account.ID, account.Currency, currency),
)

}

return account, true, nil
}

func ValidateCreateTransferRequest(req *pb.CreateTransferRequest) (violations []*errdetails.BadRequest_FieldViolation) {
if err := val.ValidateAccountID(req.GetFromAccountId()); err != nil {
violations = append(violations, fieldViolation("from_account_id", err))
}

if err := val.ValidateAccountID(req.GetToAccountId()); err != nil {
violations = append(violations, fieldViolation("to_account_id", err))
}

if err := val.ValidateTransferAmount(req.GetAmount()); err != nil {
violations = append(violations, fieldViolation("amount", err))
}

if err := val.ValidateCurrency(req.GetCurrency()); err != nil {
violations = append(violations, fieldViolation("currency", err))
}

return violations
}
Loading

0 comments on commit fcf3b8b

Please sign in to comment.