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

added dbdocs #4

Merged
merged 2 commits into from
Oct 27, 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
6 changes: 5 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ GRPC_SERVER_ADDR=:3001
SECRET_KEY=11111111222222223333333344444444
TOKEN_DURATION=15m
REFRESH_DURATION=24h
ENVIRONMENT=dev
ENVIRONMENT=dev
REDIS_ADDRESS=0.0.0.0:6379
EMAIL_SENDER_NAME=SimpleBank
[email protected]
EMAIL_SENDER_PASSWORD=btumjicgmpxzqtjp
9 changes: 9 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,14 @@ jobs:
with:
go-version: '^1.21'

- name: Install golang-migrate
run: |
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/bin
which migrate

- name: Run migrations
run: make migrate_up

- name: Test
run: make run_test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.log
.env
17 changes: 13 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
include .env

create_container:
create_postgres:
podman run --name ${DB_CONTAINER} -e POSTGRES_USER=${DB_USER} -e POSTGRES_PASSWORD=${DB_PASS} -p 5432:5432 -d postgres

create_redis:
podman run --name redis -p 6379:6379 -d redis:latest

create_database:
podman exec -it ${DB_CONTAINER} createdb --username=${DB_USER} ${DB_NAME}

Expand Down Expand Up @@ -35,11 +38,17 @@ mock_generate:

proto_gererate:
rm -rf pb/*.go
rm -rf doc/swagger/*.json
rm -rf swagger/*.json
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative --go-grpc_out=pb --go-grpc_opt=paths=source_relative --grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative --openapiv2_out=swagger --openapiv2_opt=allow_merge=true,merge_file_name=simplebank proto/*.proto

db_docs:
dbdocs build doc/db.dbml

db_schema:
dbml2sql --postgres -o doc/schema.sql doc/db.dbml

run_test:
go test -v -cover ./...
go test -v -cover -short ./...

run_server:
go run main.go
Expand All @@ -53,4 +62,4 @@ dev_deploy:
podman build -t ${BE_CONTAINER}:latest .
podman run --pod ${POD_NAME} --name ${BE_CONTAINER} -e DB_SOURCE=${MIGRATE_URL} ${BE_CONTAINER}:latest

.PHONY: create_container create_database delete_database open_database create_migration migrate_up migrate_up_last migrate_down migrate_down_last sqlc_generate mock_generate proto_gererate run_test run_server dev_deploy
.PHONY: create_postgres create_redis create_database delete_database open_database create_migration migrate_up migrate_up_last migrate_down migrate_down_last sqlc_generate mock_generate proto_gererate db_docs db_schema run_test run_server dev_deploy
94 changes: 5 additions & 89 deletions database/db/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,25 @@ import (
type Store interface {
Querier
TransferTx(ctx context.Context, arg *CreateTransferParams) (*TransferTxResult, error)
CreateUserTx(ctx context.Context, arg *CreateUserTxParams) (*CreateUserTxResult, error)
}

// Store provides all functions to execute db queries and transactions
type SQLStore struct {
type SqlStore struct {
*Queries
db *pgxpool.Pool
}

// NewStore creates a new Store
func NewStore(db *pgxpool.Pool) *SQLStore {
return &SQLStore{
func NewStore(db *pgxpool.Pool) Store {
return &SqlStore{
db: db,
Queries: New(db),
}
}

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

// execTx executes a function within a database transaction
func (s *SQLStore) ExecTx(ctx context.Context, fn func(*Queries) error) error {
func (s *SqlStore) ExecTx(ctx context.Context, fn func(*Queries) error) error {
tx, err := s.db.Begin(ctx)
if err != nil {
return err
Expand All @@ -50,79 +42,3 @@ func (s *SQLStore) ExecTx(ctx context.Context, fn func(*Queries) error) error {

return tx.Commit(ctx)
}

// 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 database transaction
func (s *SQLStore) TransferTx(ctx context.Context, arg *CreateTransferParams) (*TransferTxResult, error) {
var result TransferTxResult

err := s.ExecTx(ctx, func(q *Queries) error {

transfer, err := q.CreateTransfer(ctx, arg)
if err != nil {
return err
}

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

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

var fromAccount *Account
var toAccount *Account

if arg.FromAccountID < arg.ToAccountID {
fromAccount, toAccount, err = addMoney(ctx, q, arg.FromAccountID, -arg.Amount, arg.ToAccountID, arg.Amount)
if err != nil {
return err
}
} else {
toAccount, fromAccount, err = addMoney(ctx, q, arg.ToAccountID, arg.Amount, arg.FromAccountID, -arg.Amount)
if err != nil {
return err
}
}

result.Transfer = *transfer
result.FromEntry = *fromEntry
result.ToEntry = *toEntry
result.FromAccount = *fromAccount
result.ToAccount = *toAccount

return nil
})

return &result, err
}

func addMoney(ctx context.Context, q *Queries, accountID1, amount1, accountID2, 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,
})
if err != nil {
return
}

return
}
38 changes: 38 additions & 0 deletions database/db/tx_create_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package db

import "context"

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

// CreateUserTxResult is the result of the create user tracsaction
type CreateUserTxResult struct {
User *User
}

// CreateUserTx 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 database transaction
func (s *SqlStore) CreateUserTx(ctx context.Context, arg *CreateUserTxParams) (*CreateUserTxResult, error) {
var result CreateUserTxResult

err := s.ExecTx(ctx, func(q *Queries) error {

user, err := q.CreateUser(ctx, &arg.CreateUserParams)
if err != nil {
return err
}

err = arg.AfterCreate(user)
if err != nil {
return err
}

result.User = user
return nil
})

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

import "context"

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

// 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 database transaction
func (s *SqlStore) TransferTx(ctx context.Context, arg *CreateTransferParams) (*TransferTxResult, error) {
var result TransferTxResult

err := s.ExecTx(ctx, func(q *Queries) error {

transfer, err := q.CreateTransfer(ctx, arg)
if err != nil {
return err
}

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

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

var fromAccount *Account
var toAccount *Account

if arg.FromAccountID < arg.ToAccountID {
fromAccount, toAccount, err = addMoney(ctx, q, arg.FromAccountID, -arg.Amount, arg.ToAccountID, arg.Amount)
if err != nil {
return err
}
} else {
toAccount, fromAccount, err = addMoney(ctx, q, arg.ToAccountID, arg.Amount, arg.FromAccountID, -arg.Amount)
if err != nil {
return err
}
}

result.Transfer = *transfer
result.FromEntry = *fromEntry
result.ToEntry = *toEntry
result.FromAccount = *fromAccount
result.ToAccount = *toAccount

return nil
})

return &result, err
}

func addMoney(ctx context.Context, q *Queries, accountID1, amount1, accountID2, 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,
})
if err != nil {
return
}

return
}
15 changes: 15 additions & 0 deletions database/mockdb/store.go

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

53 changes: 53 additions & 0 deletions doc/db.dbml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
Project SimpleBank {
database_type: 'PostgreSQL'
Note: '''
# SimpleBank Database
'''
}

Table users {
username text [pk]
hashed_password text [not null]
full_name text [not null]
email text [not null, unique]
password_changed_at timestamptz [not null, default: '0001-01-01 00:00:00Z']
created_at timestamptz [not null, default: `now()`]
}

Table accounts {
id bigserial [pk]
owner text [not null, ref: > users.username]
balance bigint [not null]
currency text [not null]
created_at timestamptz [not null, default: `now()`]

Indexes {
owner
(owner, currency) [unique]
}
}

Table entries {
id bigserial [pk]
account_id bigint [not null, ref: > accounts.id]
amount bigint [not null, note: "can be negative or positive"]
created_at timestamptz [not null, default: `now()`]

Indexes {
account_id
}
}

Table transfers {
id bigserial [pk]
from_account_id bigint [not null, ref: > accounts.id]
to_account_id bigint [not null, ref: > accounts.id]
amount bigint [not null, note: "must be positive"]
created_at timestamptz [not null, default: `now()`]

Indexes {
from_account_id
to_account_id
(from_account_id, to_account_id)
}
}
Loading