Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: handle partial user info updates #16

Merged
merged 9 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ proto:
evans:
evans --host localhost --port 9090 -r repl

.PHONY: postgres createdb dropdb migrateup migratedown migrateup1 migratedown1 db_docs db_schema sqlc test server mock proto evans
redis:
docker run --name redis -p 6379:6379 -d redis:7.2.3-alpine

.PHONY: postgres createdb dropdb migrateup migratedown migrateup1 migratedown1 db_docs db_schema sqlc test server mock proto evans redis
4 changes: 3 additions & 1 deletion app.env
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
ENVIRONMENT=development
DB_DRIVER=postgres
DB_SOURCE=postgresql://root:secret@localhost:5432/bankofasia?sslmode=disable
MIGRATION_URL=file://db/migration
HTTP_SERVER_ADDRESS=0.0.0.0:8080
GRPC_SERVER_ADDRESS=0.0.0.0:9090
TOKEN_SYMMETRIC_KEY=01234567890123456789012345678901
ACCESS_TOKEN_DURATION=15m
REFRESH_TOKEN_DURATION=24h
REFRESH_TOKEN_DURATION=24h
REDIS_ADDRESS=0.0.0.0:6379
30 changes: 30 additions & 0 deletions db/mock/store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions db/query/user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@ RETURNING *;
SELECT * FROM users
WHERE username = $1 LIMIT 1;

-- name: UpdateUser :one
UPDATE users
SET
hashed_password = COALESCE(sqlc.narg(hashed_password), hashed_password),
password_changed_at = COALESCE(sqlc.narg(password_changed_at), password_changed_at),
full_name = COALESCE(sqlc.narg(full_name), full_name),
email = COALESCE(sqlc.narg(email), email)
WHERE
username = sqlc.arg(username)
RETURNING *;
2 changes: 1 addition & 1 deletion db/sqlc/account.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion db/sqlc/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion db/sqlc/entry.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion db/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion db/sqlc/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion db/sqlc/session.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 1 addition & 84 deletions db/sqlc/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type Store interface {
Querier
TransferTx(ctx context.Context, arg TransferTxParams) (TransferTxResult, error)
CreateUserTx(ctx context.Context, arg CreateUserTxParams) (CreateUserTxResult, error)
}

// SQLStore provides all functions to execute SQL queries and transactions
Expand Down Expand Up @@ -42,87 +43,3 @@ func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) erro

return tx.Commit()
}

// TransferTxParams contains all input parameters of the transfer transaction
type TransferTxParams struct {
FromAccountID int64 `json:from_account_id`
ToAccountID int64 `json:to_account_id`
Amount int64 `json:amount`
}

// TransferTxResult is the result of the transfer transaction
type TransferTxResult struct {
Transfer Transfer `json:transfer`
FromAccount Account `json:from_account`
ToAccount Account `json:to_account`
FromEntry Entry `json:from_entry`
ToEntry Entry `json:to_entry`
}

// TransferTx performs a money transfer from one account to the other
// It creates a transfer record, add account entries, and update accounts' balance within a single DB transaction
func (store *SQLStore) TransferTx(ctx context.Context, arg TransferTxParams) (TransferTxResult, error) {
var result TransferTxResult

err := store.execTx(ctx, func(q *Queries) error {
var err error

result.Transfer, err = q.CreateTransfer(ctx, CreateTransferParams{
FromAccountID: arg.FromAccountID,
ToAccountID: arg.ToAccountID,
Amount: arg.Amount,
})
if err != nil {
return err
}

result.FromEntry, err = q.CreateEntry(ctx, CreateEntryParams{
AccountID: arg.FromAccountID,
Amount: -arg.Amount,
})
if err != nil {
return err
}

result.ToEntry, err = q.CreateEntry(ctx, CreateEntryParams{
AccountID: arg.ToAccountID,
Amount: arg.Amount,
})
if err != nil {
return err
}

if arg.FromAccountID < arg.ToAccountID {
result.FromAccount, result.ToAccount, err = addMoney(ctx, q, arg.FromAccountID, -arg.Amount, arg.ToAccountID, arg.Amount)
} else {
result.ToAccount, result.FromAccount, err = addMoney(ctx, q, arg.ToAccountID, arg.Amount, arg.FromAccountID, -arg.Amount)
}

return nil
})

return result, err
}

func addMoney(
ctx context.Context,
q *Queries,
accountID1 int64,
amount1 int64,
accountID2 int64,
amount2 int64,
) (account1 Account, account2 Account, err error) {
account1, err = q.AddAccountBalance(ctx, AddAccountBalanceParams{
ID: accountID1,
Amount: amount1,
})
if err != nil {
return
}

account2, err = q.AddAccountBalance(ctx, AddAccountBalanceParams{
ID: accountID2,
Amount: amount2,
})
return
}
2 changes: 1 addition & 1 deletion db/sqlc/transfer.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions db/sqlc/tx_create_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package db

import "context"

type CreateUserTxParams struct {
CreateUserParams
AfterCreate func(user User) error
}

type CreateUserTxResult struct {
User User
}

func (store *SQLStore) CreateUserTx(ctx context.Context, arg CreateUserTxParams) (CreateUserTxResult, error) {
var result CreateUserTxResult

err := store.execTx(ctx, func(q *Queries) error {
var err error

result.User, err = q.CreateUser(ctx, arg.CreateUserParams)
if err != nil {
return err
}

return arg.AfterCreate(result.User)
})

return result, err
}
87 changes: 87 additions & 0 deletions db/sqlc/tx_transfer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package db

import "context"

// TransferTxParams contains all input parameters of the transfer transaction
type TransferTxParams struct {
FromAccountID int64 `json:from_account_id`
ToAccountID int64 `json:to_account_id`
Amount int64 `json:amount`
}

// TransferTxResult is the result of the transfer transaction
type TransferTxResult struct {
Transfer Transfer `json:transfer`
FromAccount Account `json:from_account`
ToAccount Account `json:to_account`
FromEntry Entry `json:from_entry`
ToEntry Entry `json:to_entry`
}

// TransferTx performs a money transfer from one account to the other
// It creates a transfer record, add account entries, and update accounts' balance within a single DB transaction
func (store *SQLStore) TransferTx(ctx context.Context, arg TransferTxParams) (TransferTxResult, error) {
var result TransferTxResult

err := store.execTx(ctx, func(q *Queries) error {
var err error

result.Transfer, err = q.CreateTransfer(ctx, CreateTransferParams{
FromAccountID: arg.FromAccountID,
ToAccountID: arg.ToAccountID,
Amount: arg.Amount,
})
if err != nil {
return err
}

result.FromEntry, err = q.CreateEntry(ctx, CreateEntryParams{
AccountID: arg.FromAccountID,
Amount: -arg.Amount,
})
if err != nil {
return err
}

result.ToEntry, err = q.CreateEntry(ctx, CreateEntryParams{
AccountID: arg.ToAccountID,
Amount: arg.Amount,
})
if err != nil {
return err
}

if arg.FromAccountID < arg.ToAccountID {
result.FromAccount, result.ToAccount, err = addMoney(ctx, q, arg.FromAccountID, -arg.Amount, arg.ToAccountID, arg.Amount)
} else {
result.ToAccount, result.FromAccount, err = addMoney(ctx, q, arg.ToAccountID, arg.Amount, arg.FromAccountID, -arg.Amount)
}

return nil
})

return result, err
}

func addMoney(
ctx context.Context,
q *Queries,
accountID1 int64,
amount1 int64,
accountID2 int64,
amount2 int64,
) (account1 Account, account2 Account, err error) {
account1, err = q.AddAccountBalance(ctx, AddAccountBalanceParams{
ID: accountID1,
Amount: amount1,
})
if err != nil {
return
}

account2, err = q.AddAccountBalance(ctx, AddAccountBalanceParams{
ID: accountID2,
Amount: amount2,
})
return
}
Loading