From 7405023336211532ac9b5ae88f77ca4b0303706e Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Thu, 3 Apr 2025 23:57:59 +0300 Subject: [PATCH 1/3] setting up the wallet service --- db/migrations/000001_fortune.down.sql | 7 +- db/migrations/000001_fortune.up.sql | 43 ++++- db/query/transactions.sql | 14 ++ db/query/wallet.sql | 34 ++++ gen/db/models.go | 31 ++++ gen/db/transactions.sql.go | 132 +++++++++++++++ gen/db/wallet.sql.go | 199 +++++++++++++++++++++++ internal/domain/branch.go | 19 ++- internal/domain/transaction.go | 22 +++ internal/domain/wallet.go | 41 +++++ internal/repository/transaction.go | 76 +++++++++ internal/repository/wallet.go | 124 ++++++++++++++ internal/services/transaction/port.go | 15 ++ internal/services/transaction/service.go | 33 ++++ internal/services/wallet/port.go | 17 ++ internal/services/wallet/service.go | 45 +++++ 16 files changed, 836 insertions(+), 16 deletions(-) create mode 100644 db/query/transactions.sql create mode 100644 db/query/wallet.sql create mode 100644 gen/db/transactions.sql.go create mode 100644 gen/db/wallet.sql.go create mode 100644 internal/domain/transaction.go create mode 100644 internal/domain/wallet.go create mode 100644 internal/repository/transaction.go create mode 100644 internal/repository/wallet.go create mode 100644 internal/services/transaction/port.go create mode 100644 internal/services/transaction/service.go create mode 100644 internal/services/wallet/port.go create mode 100644 internal/services/wallet/service.go diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index 2109826..248811e 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -74,6 +74,9 @@ DROP TYPE IF EXISTS ua_status; DROP TYPE IF EXISTS ua_registaration_type; -- Drop FortuneBet -DROP TABLE IF EXIST tickets; -DROP TABLE IF EXIST bets; +DROP TABLE IF EXISTS tickets; +DROP TABLE IF EXISTS bets; +DROP TABLE IF EXISTS wallets; +DROP TABLE IF EXISTS transactions; +DROP TABLE IF EXISTS customer_wallets; diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 322bed9..cfee00c 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -47,8 +47,8 @@ CREATE TABLE IF NOT EXISTS bets ( branch_id BIGINT, user_id BIGINT, cashed_out BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP, - updated_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_shop_bet BOOLEAN NOT NULL, CHECK (user_id IS NOT NULL OR branch_id IS NOT NULL) ); @@ -57,11 +57,10 @@ CREATE TABLE IF NOT EXISTS tickets ( id BIGSERIAL PRIMARY KEY, amount BIGINT NULL, total_odds REAL NOT NULL, - created_at TIMESTAMP, - updated_at TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); - -- CREATE TABLE IF NOT EXISTS bet_outcomes ( -- id BIGSERIAL PRIMARY KEY, -- bet_id BIGINT NOT NULL, @@ -74,6 +73,40 @@ CREATE TABLE IF NOT EXISTS tickets ( -- outcome_id BIGINT NOT NULL, -- ); +CREATE TABLE IF NOT EXISTS wallets ( + id BIGSERIAL PRIMARY KEY, + balance BIGINT NOT NULL, + is_withdraw BOOLEAN NOT NULL, + is_bettable BOOLEAN NOT NULL, + user_id BIGINT NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + +CREATE TABLE IF NOT EXISTS customer_wallets ( + id BIGSERIAL PRIMARY KEY, + customer_id BIGINT NOT NULL, + company_id BIGINT NOT NULL, + regular_wallet_id BIGINT NOT NULL, + static_wallet_id BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE (customer_id, company_id) +); + +CREATE TABLE IF NOT EXISTS transactions ( + id BIGSERIAL PRIMARY KEY, + amount BIGINT NOT NULL, + transaction_type VARCHAR(255) NOT NULL, + wallet_id BIGINT NOT NULL, + verified BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + + ----------------------------------------------seed data------------------------------------------------------------- -------------------------------------- DO NOT USE IN PRODUCTION------------------------------------------------- diff --git a/db/query/transactions.sql b/db/query/transactions.sql new file mode 100644 index 0000000..a2beaa8 --- /dev/null +++ b/db/query/transactions.sql @@ -0,0 +1,14 @@ +-- name: CreateTransaction :one +INSERT INTO transactions (amount, transaction_type, wallet_id) VALUES ($1, $2, $3) RETURNING *; + +-- name: GetAllTransactions :many +SELECT * FROM transactions; + +-- name: GetTransactionsByWallet :many +SELECT * FROM transactions WHERE wallet_id = $1; + +-- name: GetTransactionByID :one +SELECT * FROM transactions WHERE id = $1; + +-- name: UpdateTransferVerification :exec +UPDATE transactions SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; diff --git a/db/query/wallet.sql b/db/query/wallet.sql new file mode 100644 index 0000000..a59a3e7 --- /dev/null +++ b/db/query/wallet.sql @@ -0,0 +1,34 @@ +-- name: CreateWallet :one +INSERT INTO wallets (balance, is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3, $4) RETURNING *; + +-- name: CreateCustomerWallet :one +INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING *; + +-- name: GetAllWallets :many +SELECT * FROM wallets; + +-- name: GetWalletByID :one +SELECT * FROM wallets WHERE id = $1; + +-- name: GetCustomerWallet :one +SELECT + cw.id, + cw.customer_id, + cw.company_id, + rw.id AS regular_id, + rw.balance AS regular_balance, + sw.id AS static_id, + sw.balance AS static_balance +FROM customer_wallets cw +JOIN wallets rw ON cw.regular_wallet_id = rw.id +JOIN wallets sw ON cw.static_wallet_id = sw.id +WHERE cw.customer_id = $1 AND cw.company_id = $2; + +-- name: UpdateBalance :exec +UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; + +-- name: UpdateWalletActive :exec +UPDATE wallets SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; + + + diff --git a/gen/db/models.go b/gen/db/models.go index 8703486..e8e4c84 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -23,6 +23,16 @@ type Bet struct { IsShopBet bool } +type CustomerWallet struct { + ID int64 + CustomerID int64 + CompanyID int64 + RegularWalletID int64 + StaticWalletID int64 + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} + type Notification struct { ID string RecipientID string @@ -69,6 +79,16 @@ type Ticket struct { UpdatedAt pgtype.Timestamp } +type Transaction struct { + ID int64 + Amount int64 + TransactionType string + WalletID int64 + Verified bool + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} + type User struct { ID int64 FirstName string @@ -84,3 +104,14 @@ type User struct { SuspendedAt pgtype.Timestamptz Suspended bool } + +type Wallet struct { + ID int64 + Balance int64 + IsWithdraw bool + IsBettable bool + UserID int64 + IsActive bool + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} diff --git a/gen/db/transactions.sql.go b/gen/db/transactions.sql.go new file mode 100644 index 0000000..d56aebc --- /dev/null +++ b/gen/db/transactions.sql.go @@ -0,0 +1,132 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: transactions.sql + +package dbgen + +import ( + "context" +) + +const CreateTransaction = `-- name: CreateTransaction :one +INSERT INTO transactions (amount, transaction_type, wallet_id) VALUES ($1, $2, $3) RETURNING id, amount, transaction_type, wallet_id, verified, created_at, updated_at +` + +type CreateTransactionParams struct { + Amount int64 + TransactionType string + WalletID int64 +} + +func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { + row := q.db.QueryRow(ctx, CreateTransaction, arg.Amount, arg.TransactionType, arg.WalletID) + var i Transaction + err := row.Scan( + &i.ID, + &i.Amount, + &i.TransactionType, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetAllTransactions = `-- name: GetAllTransactions :many +SELECT id, amount, transaction_type, wallet_id, verified, created_at, updated_at FROM transactions +` + +func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) { + rows, err := q.db.Query(ctx, GetAllTransactions) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.TransactionType, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetTransactionByID = `-- name: GetTransactionByID :one +SELECT id, amount, transaction_type, wallet_id, verified, created_at, updated_at FROM transactions WHERE id = $1 +` + +func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) { + row := q.db.QueryRow(ctx, GetTransactionByID, id) + var i Transaction + err := row.Scan( + &i.ID, + &i.Amount, + &i.TransactionType, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetTransactionsByWallet = `-- name: GetTransactionsByWallet :many +SELECT id, amount, transaction_type, wallet_id, verified, created_at, updated_at FROM transactions WHERE wallet_id = $1 +` + +func (q *Queries) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]Transaction, error) { + rows, err := q.db.Query(ctx, GetTransactionsByWallet, walletID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.TransactionType, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const UpdateTransferVerification = `-- name: UpdateTransferVerification :exec +UPDATE transactions SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +` + +type UpdateTransferVerificationParams struct { + Verified bool + ID int64 +} + +func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error { + _, err := q.db.Exec(ctx, UpdateTransferVerification, arg.Verified, arg.ID) + return err +} diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go new file mode 100644 index 0000000..c943cd2 --- /dev/null +++ b/gen/db/wallet.sql.go @@ -0,0 +1,199 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: wallet.sql + +package dbgen + +import ( + "context" +) + +const CreateCustomerWallet = `-- name: CreateCustomerWallet :one +INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING id, customer_id, company_id, regular_wallet_id, static_wallet_id, created_at, updated_at +` + +type CreateCustomerWalletParams struct { + CustomerID int64 + CompanyID int64 + RegularWalletID int64 + StaticWalletID int64 +} + +func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWalletParams) (CustomerWallet, error) { + row := q.db.QueryRow(ctx, CreateCustomerWallet, + arg.CustomerID, + arg.CompanyID, + arg.RegularWalletID, + arg.StaticWalletID, + ) + var i CustomerWallet + err := row.Scan( + &i.ID, + &i.CustomerID, + &i.CompanyID, + &i.RegularWalletID, + &i.StaticWalletID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateWallet = `-- name: CreateWallet :one +INSERT INTO wallets (balance, is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3, $4) RETURNING id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at +` + +type CreateWalletParams struct { + Balance int64 + IsWithdraw bool + IsBettable bool + UserID int64 +} + +func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wallet, error) { + row := q.db.QueryRow(ctx, CreateWallet, + arg.Balance, + arg.IsWithdraw, + arg.IsBettable, + arg.UserID, + ) + var i Wallet + err := row.Scan( + &i.ID, + &i.Balance, + &i.IsWithdraw, + &i.IsBettable, + &i.UserID, + &i.IsActive, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetAllWallets = `-- name: GetAllWallets :many +SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at FROM wallets +` + +func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { + rows, err := q.db.Query(ctx, GetAllWallets) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Wallet + for rows.Next() { + var i Wallet + if err := rows.Scan( + &i.ID, + &i.Balance, + &i.IsWithdraw, + &i.IsBettable, + &i.UserID, + &i.IsActive, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCustomerWallet = `-- name: GetCustomerWallet :one +SELECT + cw.id, + cw.customer_id, + cw.company_id, + rw.id AS regular_id, + rw.balance AS regular_balance, + sw.id AS static_id, + sw.balance AS static_balance +FROM customer_wallets cw +JOIN wallets rw ON cw.regular_wallet_id = rw.id +JOIN wallets sw ON cw.static_wallet_id = sw.id +WHERE cw.customer_id = $1 AND cw.company_id = $2 +` + +type GetCustomerWalletParams struct { + CustomerID int64 + CompanyID int64 +} + +type GetCustomerWalletRow struct { + ID int64 + CustomerID int64 + CompanyID int64 + RegularID int64 + RegularBalance int64 + StaticID int64 + StaticBalance int64 +} + +func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletParams) (GetCustomerWalletRow, error) { + row := q.db.QueryRow(ctx, GetCustomerWallet, arg.CustomerID, arg.CompanyID) + var i GetCustomerWalletRow + err := row.Scan( + &i.ID, + &i.CustomerID, + &i.CompanyID, + &i.RegularID, + &i.RegularBalance, + &i.StaticID, + &i.StaticBalance, + ) + return i, err +} + +const GetWalletByID = `-- name: GetWalletByID :one +SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at FROM wallets WHERE id = $1 +` + +func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { + row := q.db.QueryRow(ctx, GetWalletByID, id) + var i Wallet + err := row.Scan( + &i.ID, + &i.Balance, + &i.IsWithdraw, + &i.IsBettable, + &i.UserID, + &i.IsActive, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateBalance = `-- name: UpdateBalance :exec +UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +` + +type UpdateBalanceParams struct { + Balance int64 + ID int64 +} + +func (q *Queries) UpdateBalance(ctx context.Context, arg UpdateBalanceParams) error { + _, err := q.db.Exec(ctx, UpdateBalance, arg.Balance, arg.ID) + return err +} + +const UpdateWalletActive = `-- name: UpdateWalletActive :exec +UPDATE wallets SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +` + +type UpdateWalletActiveParams struct { + IsActive bool + ID int64 +} + +func (q *Queries) UpdateWalletActive(ctx context.Context, arg UpdateWalletActiveParams) error { + _, err := q.db.Exec(ctx, UpdateWalletActive, arg.IsActive, arg.ID) + return err +} diff --git a/internal/domain/branch.go b/internal/domain/branch.go index 54bdcf7..dc4a07f 100644 --- a/internal/domain/branch.go +++ b/internal/domain/branch.go @@ -1,13 +1,14 @@ package domain - type Branch struct { - ID int64 - Name string - Location string - BranchManagerID int64 - IsSelfOwned bool + ID int64 + Name string + Location string + WalletID int64 + BranchManagerID int64 + IsSelfOwned bool IsSupportingSportBook bool - IsSupportingVirtual bool - IsSupportingGameZone bool -} \ No newline at end of file + IsSupportingVirtual bool + IsSupportingGameZone bool +} + diff --git a/internal/domain/transaction.go b/internal/domain/transaction.go new file mode 100644 index 0000000..8fcd1dc --- /dev/null +++ b/internal/domain/transaction.go @@ -0,0 +1,22 @@ +package domain + +type TransactionType string + +const ( + DEPOSIT TransactionType = "deposit" + WITHDRAW TransactionType = "withdraw" +) + +type Transaction struct { + ID int64 + Amount Currency + Type TransactionType + Verified bool + WalletID int64 +} + +type CreateTransaction struct { + Amount Currency + Type TransactionType + WalletID int64 +} diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go new file mode 100644 index 0000000..f738104 --- /dev/null +++ b/internal/domain/wallet.go @@ -0,0 +1,41 @@ +package domain + +type Wallet struct { + ID int64 + Balance Currency + IsWithdraw bool + IsBettable bool + IsActive bool + UserID int64 +} + +type CustomerWallet struct { + ID int64 + RegularID int64 + StaticID int64 + CustomerID int64 + CompanyID int64 +} +type GetCustomerWallet struct { + ID int64 + RegularID int64 + RegularBalance Currency + StaticID int64 + StaticBalance Currency + CustomerID int64 + CompanyID int64 +} + +type CreateWallet struct { + Balance Currency + IsWithdraw bool + IsBettable bool + UserID int64 +} + +type CreateCustomerWallet struct { + CustomerID int64 + CompanyID int64 + RegularWalletID int64 + StaticWalletID int64 +} diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go new file mode 100644 index 0000000..bef1090 --- /dev/null +++ b/internal/repository/transaction.go @@ -0,0 +1,76 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { + return domain.Transaction{ + ID: transaction.ID, + Amount: domain.Currency(transaction.Amount), + Type: domain.TransactionType(transaction.TransactionType), + Verified: transaction.Verified, + WalletID: transaction.WalletID, + } +} + +func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { + return dbgen.CreateTransactionParams{ + Amount: int64(transaction.Amount), + TransactionType: string(transaction.Type), + WalletID: transaction.WalletID, + } +} + +func (s *Store) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { + newTransaction, err := s.queries.CreateTransaction(ctx, convertCreateTransaction(transaction)) + if err != nil { + return domain.Transaction{}, err + } + return convertDBTransaction(newTransaction), nil +} + +func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { + transactions, err := s.queries.GetAllTransactions(ctx) + if err != nil { + return nil, err + } + var result []domain.Transaction + for _, transaction := range transactions { + result = append(result, convertDBTransaction(transaction)) + } + return result, nil +} +func (s *Store) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) { + transactions, err := s.queries.GetTransactionsByWallet(ctx, walletID) + if err != nil { + return nil, err + } + + var result []domain.Transaction + for _, transaction := range transactions { + result = append(result, convertDBTransaction(transaction)) + } + return result, nil +} + + +func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error){ + transaction, err := s.queries.GetTransactionByID(ctx, id); + if err != nil { + return domain.Transaction{}, nil + } + return convertDBTransaction(transaction), nil +} + +func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error{ + err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ + ID: id, + Verified: verified, + }) + + return err +} diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go new file mode 100644 index 0000000..13514fe --- /dev/null +++ b/internal/repository/wallet.go @@ -0,0 +1,124 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func convertDBWallet(wallet dbgen.Wallet) domain.Wallet { + return domain.Wallet{ + ID: wallet.ID, + Balance: domain.Currency(wallet.Balance), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + } +} + +func convertCreateWallet(wallet domain.CreateWallet) dbgen.CreateWalletParams { + return dbgen.CreateWalletParams{ + Balance: int64(wallet.Balance), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + UserID: wallet.UserID, + } +} + +func convertDBCustomerWallet(customerWallet dbgen.CustomerWallet) domain.CustomerWallet { + return domain.CustomerWallet{ + ID: customerWallet.ID, + RegularID: customerWallet.RegularWalletID, + StaticID: customerWallet.StaticWalletID, + CustomerID: customerWallet.CustomerID, + CompanyID: customerWallet.CompanyID, + } +} +func convertCreateCustomerWallet(customerWallet domain.CreateCustomerWallet) dbgen.CreateCustomerWalletParams { + return dbgen.CreateCustomerWalletParams{ + CustomerID: customerWallet.CustomerID, + CompanyID: customerWallet.CompanyID, + RegularWalletID: customerWallet.RegularWalletID, + StaticWalletID: customerWallet.StaticWalletID, + } +} + +func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domain.GetCustomerWallet { + return domain.GetCustomerWallet{ + ID: customerWallet.ID, + RegularID: customerWallet.RegularID, + RegularBalance: domain.Currency(customerWallet.RegularBalance), + StaticID: customerWallet.StaticID, + StaticBalance: domain.Currency(customerWallet.StaticBalance), + CustomerID: customerWallet.CustomerID, + CompanyID: customerWallet.CompanyID, + } +} + +func (s *Store) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { + newWallet, err := s.queries.CreateWallet(ctx, convertCreateWallet(wallet)) + if err != nil { + return domain.Wallet{}, err + } + return convertDBWallet(newWallet), nil +} + +func (s *Store) CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error) { + newCustomerWallet, err := s.queries.CreateCustomerWallet(ctx, convertCreateCustomerWallet(customerWallet)) + if err != nil { + return domain.CustomerWallet{}, err + } + return convertDBCustomerWallet(newCustomerWallet), nil +} + +func (s *Store) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { + wallet, err := s.queries.GetWalletByID(ctx, id) + + if err != nil { + return domain.Wallet{}, err + } + return convertDBWallet(wallet), nil +} + +func (s *Store) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { + wallets, err := s.queries.GetAllWallets(ctx) + if err != nil { + return nil, err + } + + var result []domain.Wallet + for _, wallet := range wallets { + result = append(result, convertDBWallet(wallet)) + } + return result, nil +} + +func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { + customerWallet, err := s.queries.GetCustomerWallet(ctx, dbgen.GetCustomerWalletParams{ + CustomerID: customerID, + CompanyID: companyID, + }) + + if err != nil { + return domain.GetCustomerWallet{}, err + } + return convertDBGetCustomerWallet(customerWallet), nil +} + +func (s *Store) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { + err := s.queries.UpdateBalance(ctx, dbgen.UpdateBalanceParams{ + ID: id, + Balance: int64(balance), + }) + return err +} + +func (s *Store) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { + err := s.queries.UpdateWalletActive(ctx, dbgen.UpdateWalletActiveParams{ + ID: id, + IsActive: isActive, + }) + return err +} diff --git a/internal/services/transaction/port.go b/internal/services/transaction/port.go new file mode 100644 index 0000000..9d47cb4 --- /dev/null +++ b/internal/services/transaction/port.go @@ -0,0 +1,15 @@ +package transaction + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type TransactionStore interface { + CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) + GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) + GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) + GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) + UpdateTransferVerification(ctx context.Context, id int64, verified bool) error +} diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go new file mode 100644 index 0000000..3d57afd --- /dev/null +++ b/internal/services/transaction/service.go @@ -0,0 +1,33 @@ +package transaction + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + transactionStore TransactionStore +} + +func NewService(transactionStore TransactionStore) *Service { + return &Service{ + transactionStore: transactionStore, + } +} + +func (s *Service) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { + return s.transactionStore.CreateTransaction(ctx, transaction) +} + +func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { + return s.transactionStore.GetAllTransactions(ctx) +} + +func (s *Service) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { + return s.transactionStore.GetTransactionByID(ctx, id) +} + +func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { + return s.transactionStore.UpdateTransferVerification(ctx, id, verified) +} diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go new file mode 100644 index 0000000..3ac6a12 --- /dev/null +++ b/internal/services/wallet/port.go @@ -0,0 +1,17 @@ +package wallet + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type WalletStore interface { + CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) + CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error) + GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) + GetAllWallets(ctx context.Context) ([]domain.Wallet, error) + GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) + UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error + UpdateWalletActive(ctx context.Context, id int64, isActive bool) error +} diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go new file mode 100644 index 0000000..38d2031 --- /dev/null +++ b/internal/services/wallet/service.go @@ -0,0 +1,45 @@ +package wallet + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + walletStore WalletStore +} + +func NewService(walletStore WalletStore) *Service { + return &Service{ + walletStore: walletStore, + } +} + +func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { + return s.walletStore.CreateWallet(ctx, wallet) +} + +func (s *Service) CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error) { + return s.walletStore.CreateCustomerWallet(ctx, customerWallet) +} + +func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { + return s.walletStore.GetWalletByID(ctx, id) +} + +func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { + return s.walletStore.GetAllWallets(ctx) +} + +func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { + return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) +} + +func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { + return s.walletStore.UpdateBalance(ctx, id, balance) +} + +func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { + return s.walletStore.UpdateWalletActive(ctx, id, isActive) +} From 675597c093e92718c59710387b673d2cba85440d Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Fri, 4 Apr 2025 21:51:15 +0300 Subject: [PATCH 2/3] wallets and transaction routes --- cmd/main.go | 6 +- db/migrations/000001_fortune.down.sql | 1 + db/migrations/000001_fortune.up.sql | 25 +- db/query/transactions.sql | 13 +- db/query/transfer.sql | 14 + db/query/wallet.sql | 10 +- docs/docs.go | 497 +++++++++++++++++- docs/swagger.json | 497 +++++++++++++++++- docs/swagger.yaml | 337 +++++++++++- gen/db/models.go | 23 +- gen/db/transactions.sql.go | 117 +++-- gen/db/transfer.sql.go | 132 +++++ gen/db/wallet.sql.go | 70 ++- internal/domain/transaction.go | 44 +- internal/domain/transfer.go | 23 + internal/domain/wallet.go | 32 +- internal/repository/bet.go | 2 +- internal/repository/ticket.go | 2 +- internal/repository/transaction.go | 84 +-- internal/repository/transfer.go | 77 +++ internal/repository/wallet.go | 38 +- internal/services/transaction/port.go | 5 +- internal/services/transaction/service.go | 14 +- internal/services/transfer/chapa.go | 1 + internal/services/transfer/port.go | 15 + internal/services/transfer/service.go | 33 ++ internal/services/user/service.go | 1 + internal/services/wallet/port.go | 1 + internal/services/wallet/service.go | 61 ++- internal/web_server/app.go | 47 +- internal/web_server/handlers/bet_handler.go | 2 +- .../web_server/handlers/ticket_handler.go | 2 +- .../handlers/transaction_handler.go | 232 ++++++++ internal/web_server/handlers/user.go | 15 +- .../web_server/handlers/wallet_handler.go | 148 ++++++ internal/web_server/routes.go | 15 +- 36 files changed, 2431 insertions(+), 205 deletions(-) create mode 100644 db/query/transfer.sql create mode 100644 gen/db/transfer.sql.go create mode 100644 internal/domain/transfer.go create mode 100644 internal/repository/transfer.go create mode 100644 internal/services/transfer/chapa.go create mode 100644 internal/services/transfer/port.go create mode 100644 internal/services/transfer/service.go create mode 100644 internal/web_server/handlers/transaction_handler.go create mode 100644 internal/web_server/handlers/wallet_handler.go diff --git a/cmd/main.go b/cmd/main.go index 93b4fa2..b6c8f24 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,7 +14,9 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" @@ -57,6 +59,8 @@ func main() { userSvc := user.NewService(store, store, mockSms, mockemail) ticketSvc := ticket.NewService(store) betSvc := bet.NewService(store) + walletSvc := wallet.NewService(store) + transactionSvc := transaction.NewService(store) notificationRepo := repository.NewNotificationRepository(store) notificationSvc := notificationservice.New(notificationRepo, logger) @@ -64,7 +68,7 @@ func main() { app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{ JwtAccessKey: cfg.JwtKey, JwtAccessExpiry: cfg.AccessExpiry, - }, userSvc, ticketSvc, betSvc, notificationSvc, + }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, ) logger.Info("Starting server", "port", cfg.Port) diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index 248811e..e4d5b9a 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -77,6 +77,7 @@ DROP TYPE IF EXISTS ua_registaration_type; DROP TABLE IF EXISTS tickets; DROP TABLE IF EXISTS bets; DROP TABLE IF EXISTS wallets; +DROP TABLE IF EXISTS wallet_transfer; DROP TABLE IF EXISTS transactions; DROP TABLE IF EXISTS customer_wallets; diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index cfee00c..ed91221 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS tickets ( CREATE TABLE IF NOT EXISTS wallets ( id BIGSERIAL PRIMARY KEY, - balance BIGINT NOT NULL, + balance BIGINT NOT NULL DEFAULT 0, is_withdraw BOOLEAN NOT NULL, is_bettable BOOLEAN NOT NULL, user_id BIGINT NOT NULL, @@ -96,11 +96,30 @@ CREATE TABLE IF NOT EXISTS customer_wallets ( UNIQUE (customer_id, company_id) ); +CREATE TABLE IF NOT EXISTS wallet_transfer ( + id BIGSERIAL PRIMARY KEY, + amount BIGINT NOT NULL, + wallet_transfer VARCHAR(255) NOT NULL, + wallet_id BIGINT NOT NULL, + verified BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + CREATE TABLE IF NOT EXISTS transactions ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, - transaction_type VARCHAR(255) NOT NULL, - wallet_id BIGINT NOT NULL, + branch_id BIGINT NOT NULL, + cashier_id BIGINT NOT NULL, + bet_id BIGINT NOT NULL, + payment_option BIGINT NOT NULL, + full_name VARCHAR(255) NOT NULL, + phone_number VARCHAR(255) NOT NULL, + bank_code VARCHAR(255) NOT NULL, + beneficiary_name VARCHAR(255) NOT NULL, + account_name VARCHAR(255) NOT NULL, + account_number VARCHAR(255) NOT NULL, + reference_number VARCHAR(255) NOT NULL, verified BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP diff --git a/db/query/transactions.sql b/db/query/transactions.sql index a2beaa8..2665eaa 100644 --- a/db/query/transactions.sql +++ b/db/query/transactions.sql @@ -1,14 +1,13 @@ -- name: CreateTransaction :one -INSERT INTO transactions (amount, transaction_type, wallet_id) VALUES ($1, $2, $3) RETURNING *; +INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *; -- name: GetAllTransactions :many SELECT * FROM transactions; --- name: GetTransactionsByWallet :many -SELECT * FROM transactions WHERE wallet_id = $1; - --- name: GetTransactionByID :one +-- name: GetTransactionByID :one SELECT * FROM transactions WHERE id = $1; --- name: UpdateTransferVerification :exec -UPDATE transactions SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; +-- name: UpdateTransactionVerified :exec +UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; + + diff --git a/db/query/transfer.sql b/db/query/transfer.sql new file mode 100644 index 0000000..895ccb8 --- /dev/null +++ b/db/query/transfer.sql @@ -0,0 +1,14 @@ +-- name: CreateTransfer :one +INSERT INTO wallet_transfer (amount, wallet_transfer, wallet_id) VALUES ($1, $2, $3) RETURNING *; + +-- name: GetAllTransfers :many +SELECT * FROM wallet_transfer; + +-- name: GetTransfersByWallet :many +SELECT * FROM wallet_transfer WHERE wallet_id = $1; + +-- name: GetTransferByID :one +SELECT * FROM wallet_transfer WHERE id = $1; + +-- name: UpdateTransferVerification :exec +UPDATE wallet_transfer SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2; diff --git a/db/query/wallet.sql b/db/query/wallet.sql index a59a3e7..46f3200 100644 --- a/db/query/wallet.sql +++ b/db/query/wallet.sql @@ -1,5 +1,5 @@ -- name: CreateWallet :one -INSERT INTO wallets (balance, is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3, $4) RETURNING *; +INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING *; -- name: CreateCustomerWallet :one INSERT INTO customer_wallets (customer_id, company_id, regular_wallet_id, static_wallet_id) VALUES ($1, $2, $3, $4) RETURNING *; @@ -10,6 +10,9 @@ SELECT * FROM wallets; -- name: GetWalletByID :one SELECT * FROM wallets WHERE id = $1; +-- name: GetWalletByUserID :many +SELECT * FROM wallets WHERE user_id = $1; + -- name: GetCustomerWallet :one SELECT cw.id, @@ -18,7 +21,10 @@ SELECT rw.id AS regular_id, rw.balance AS regular_balance, sw.id AS static_id, - sw.balance AS static_balance + sw.balance AS static_balance, + rw.updated_at as regular_updated_at, + sw.updated_at as static_updated_at, + cw.created_at FROM customer_wallets cw JOIN wallets rw ON cw.regular_wallet_id = rw.id JOIN wallets sw ON cw.static_wallet_id = sw.id diff --git a/docs/docs.go b/docs/docs.go index 49956c9..f8d47cd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -525,6 +525,183 @@ const docTemplate = `{ } } }, + "/transaction": { + "get": { + "description": "Gets all the transactions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Gets all transactions", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.TransactionRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a transaction", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Create a transaction", + "parameters": [ + { + "description": "Creates transaction", + "name": "createBet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransactionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransactionRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transaction/{id}": { + "get": { + "description": "Gets a single transaction by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Gets transaction by id", + "parameters": [ + { + "type": "integer", + "description": "Transaction ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransactionRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "patch": { + "description": "Updates the cashed out field", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Updates the cashed out field", + "parameters": [ + { + "type": "integer", + "description": "Transaction ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updates Transaction Verification", + "name": "updateCashOut", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateTransactionVerifiedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/checkPhoneEmailExist": { "post": { "description": "Check if phone number or email exist", @@ -794,6 +971,139 @@ const docTemplate = `{ } } } + }, + "/wallet": { + "get": { + "description": "Retrieve all wallets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get all wallets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/wallet/{id}": { + "get": { + "description": "Retrieve wallet details by wallet ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get wallet by ID", + "parameters": [ + { + "type": "integer", + "description": "Wallet ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.WalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "patch": { + "description": "Can activate and deactivate wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Activate and Deactivate Wallet", + "parameters": [ + { + "type": "integer", + "description": "Wallet ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Wallet Active", + "name": "updateCashOut", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateWalletActiveReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } } }, "definitions": { @@ -815,20 +1125,35 @@ const docTemplate = `{ "domain.Outcome": { "type": "object" }, + "domain.PaymentOption": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "CASH_TRANSACTION", + "TELEBIRR_TRANSACTION", + "ARIFPAY_TRANSACTION", + "BANK" + ] + }, "domain.Role": { "type": "string", "enum": [ - "admin", - "customer", "super_admin", + "admin", "branch_manager", + "customer", "cashier" ], "x-enum-varnames": [ - "RoleAdmin", - "RoleCustomer", "RoleSuperAdmin", + "RoleAdmin", "RoleBranchManager", + "RoleCustomer", "RoleCashier" ] }, @@ -974,6 +1299,59 @@ const docTemplate = `{ } } }, + "handlers.CreateTransactionReq": { + "type": "object", + "properties": { + "account_name": { + "type": "string" + }, + "account_number": { + "type": "string" + }, + "amount": { + "type": "number", + "example": 100 + }, + "bank_code": { + "description": "Payment Details for bank", + "type": "string" + }, + "beneficiary_name": { + "type": "string" + }, + "bet_id": { + "type": "integer", + "example": 1 + }, + "branch_id": { + "type": "integer", + "example": 1 + }, + "cashier_id": { + "type": "integer", + "example": 1 + }, + "full_name": { + "type": "string", + "example": "John Smith" + }, + "payment_option": { + "allOf": [ + { + "$ref": "#/definitions/domain.PaymentOption" + } + ], + "example": 1 + }, + "phone_number": { + "type": "string", + "example": "0911111111" + }, + "reference_number": { + "type": "string" + } + } + }, "handlers.RegisterCodeReq": { "type": "object", "properties": { @@ -1074,6 +1452,66 @@ const docTemplate = `{ } } }, + "handlers.TransactionRes": { + "type": "object", + "properties": { + "account_name": { + "type": "string" + }, + "account_number": { + "type": "string" + }, + "amount": { + "type": "number", + "example": 100 + }, + "bank_code": { + "type": "string" + }, + "beneficiary_name": { + "type": "string" + }, + "bet_id": { + "type": "integer", + "example": 1 + }, + "branch_id": { + "type": "integer", + "example": 1 + }, + "cashier_id": { + "type": "integer", + "example": 1 + }, + "full_name": { + "type": "string", + "example": "John Smith" + }, + "id": { + "type": "integer", + "example": 1 + }, + "payment_option": { + "allOf": [ + { + "$ref": "#/definitions/domain.PaymentOption" + } + ], + "example": 1 + }, + "phone_number": { + "type": "string", + "example": "0911111111" + }, + "reference_number": { + "type": "string" + }, + "verified": { + "type": "boolean", + "example": true + } + } + }, "handlers.UpdateCashOutReq": { "type": "object", "properties": { @@ -1082,6 +1520,22 @@ const docTemplate = `{ } } }, + "handlers.UpdateTransactionVerifiedReq": { + "type": "object", + "properties": { + "verified": { + "type": "boolean" + } + } + }, + "handlers.UpdateWalletActiveReq": { + "type": "object", + "properties": { + "isActive": { + "type": "boolean" + } + } + }, "handlers.UserProfileRes": { "type": "object", "properties": { @@ -1123,6 +1577,41 @@ const docTemplate = `{ } } }, + "handlers.WalletRes": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": true + }, + "is_bettable": { + "type": "boolean", + "example": true + }, + "is_withdraw": { + "type": "boolean", + "example": true + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.loginCustomerReq": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 1fec68a..3a34879 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -517,6 +517,183 @@ } } }, + "/transaction": { + "get": { + "description": "Gets all the transactions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Gets all transactions", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.TransactionRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "description": "Creates a transaction", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Create a transaction", + "parameters": [ + { + "description": "Creates transaction", + "name": "createBet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CreateTransactionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransactionRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/transaction/{id}": { + "get": { + "description": "Gets a single transaction by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Gets transaction by id", + "parameters": [ + { + "type": "integer", + "description": "Transaction ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.TransactionRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "patch": { + "description": "Updates the cashed out field", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transaction" + ], + "summary": "Updates the cashed out field", + "parameters": [ + { + "type": "integer", + "description": "Transaction ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updates Transaction Verification", + "name": "updateCashOut", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateTransactionVerifiedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/user/checkPhoneEmailExist": { "post": { "description": "Check if phone number or email exist", @@ -786,6 +963,139 @@ } } } + }, + "/wallet": { + "get": { + "description": "Retrieve all wallets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get all wallets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/wallet/{id}": { + "get": { + "description": "Retrieve wallet details by wallet ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get wallet by ID", + "parameters": [ + { + "type": "integer", + "description": "Wallet ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.WalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "patch": { + "description": "Can activate and deactivate wallet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Activate and Deactivate Wallet", + "parameters": [ + { + "type": "integer", + "description": "Wallet ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Wallet Active", + "name": "updateCashOut", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.UpdateWalletActiveReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } } }, "definitions": { @@ -807,20 +1117,35 @@ "domain.Outcome": { "type": "object" }, + "domain.PaymentOption": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "CASH_TRANSACTION", + "TELEBIRR_TRANSACTION", + "ARIFPAY_TRANSACTION", + "BANK" + ] + }, "domain.Role": { "type": "string", "enum": [ - "admin", - "customer", "super_admin", + "admin", "branch_manager", + "customer", "cashier" ], "x-enum-varnames": [ - "RoleAdmin", - "RoleCustomer", "RoleSuperAdmin", + "RoleAdmin", "RoleBranchManager", + "RoleCustomer", "RoleCashier" ] }, @@ -966,6 +1291,59 @@ } } }, + "handlers.CreateTransactionReq": { + "type": "object", + "properties": { + "account_name": { + "type": "string" + }, + "account_number": { + "type": "string" + }, + "amount": { + "type": "number", + "example": 100 + }, + "bank_code": { + "description": "Payment Details for bank", + "type": "string" + }, + "beneficiary_name": { + "type": "string" + }, + "bet_id": { + "type": "integer", + "example": 1 + }, + "branch_id": { + "type": "integer", + "example": 1 + }, + "cashier_id": { + "type": "integer", + "example": 1 + }, + "full_name": { + "type": "string", + "example": "John Smith" + }, + "payment_option": { + "allOf": [ + { + "$ref": "#/definitions/domain.PaymentOption" + } + ], + "example": 1 + }, + "phone_number": { + "type": "string", + "example": "0911111111" + }, + "reference_number": { + "type": "string" + } + } + }, "handlers.RegisterCodeReq": { "type": "object", "properties": { @@ -1066,6 +1444,66 @@ } } }, + "handlers.TransactionRes": { + "type": "object", + "properties": { + "account_name": { + "type": "string" + }, + "account_number": { + "type": "string" + }, + "amount": { + "type": "number", + "example": 100 + }, + "bank_code": { + "type": "string" + }, + "beneficiary_name": { + "type": "string" + }, + "bet_id": { + "type": "integer", + "example": 1 + }, + "branch_id": { + "type": "integer", + "example": 1 + }, + "cashier_id": { + "type": "integer", + "example": 1 + }, + "full_name": { + "type": "string", + "example": "John Smith" + }, + "id": { + "type": "integer", + "example": 1 + }, + "payment_option": { + "allOf": [ + { + "$ref": "#/definitions/domain.PaymentOption" + } + ], + "example": 1 + }, + "phone_number": { + "type": "string", + "example": "0911111111" + }, + "reference_number": { + "type": "string" + }, + "verified": { + "type": "boolean", + "example": true + } + } + }, "handlers.UpdateCashOutReq": { "type": "object", "properties": { @@ -1074,6 +1512,22 @@ } } }, + "handlers.UpdateTransactionVerifiedReq": { + "type": "object", + "properties": { + "verified": { + "type": "boolean" + } + } + }, + "handlers.UpdateWalletActiveReq": { + "type": "object", + "properties": { + "isActive": { + "type": "boolean" + } + } + }, "handlers.UserProfileRes": { "type": "object", "properties": { @@ -1115,6 +1569,41 @@ } } }, + "handlers.WalletRes": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100 + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": true + }, + "is_bettable": { + "type": "boolean", + "example": true + }, + "is_withdraw": { + "type": "boolean", + "example": true + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer", + "example": 1 + } + } + }, "handlers.loginCustomerReq": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 4cd5fdd..d9d0d27 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -13,19 +13,31 @@ definitions: - BET_STATUS_ERROR domain.Outcome: type: object + domain.PaymentOption: + enum: + - 0 + - 1 + - 2 + - 3 + type: integer + x-enum-varnames: + - CASH_TRANSACTION + - TELEBIRR_TRANSACTION + - ARIFPAY_TRANSACTION + - BANK domain.Role: enum: - - admin - - customer - super_admin + - admin - branch_manager + - customer - cashier type: string x-enum-varnames: - - RoleAdmin - - RoleCustomer - RoleSuperAdmin + - RoleAdmin - RoleBranchManager + - RoleCustomer - RoleCashier handlers.BetRes: properties: @@ -123,6 +135,42 @@ definitions: example: 1234 type: integer type: object + handlers.CreateTransactionReq: + properties: + account_name: + type: string + account_number: + type: string + amount: + example: 100 + type: number + bank_code: + description: Payment Details for bank + type: string + beneficiary_name: + type: string + bet_id: + example: 1 + type: integer + branch_id: + example: 1 + type: integer + cashier_id: + example: 1 + type: integer + full_name: + example: John Smith + type: string + payment_option: + allOf: + - $ref: '#/definitions/domain.PaymentOption' + example: 1 + phone_number: + example: "0911111111" + type: string + reference_number: + type: string + type: object handlers.RegisterCodeReq: properties: email: @@ -193,11 +241,62 @@ definitions: example: 4.22 type: number type: object + handlers.TransactionRes: + properties: + account_name: + type: string + account_number: + type: string + amount: + example: 100 + type: number + bank_code: + type: string + beneficiary_name: + type: string + bet_id: + example: 1 + type: integer + branch_id: + example: 1 + type: integer + cashier_id: + example: 1 + type: integer + full_name: + example: John Smith + type: string + id: + example: 1 + type: integer + payment_option: + allOf: + - $ref: '#/definitions/domain.PaymentOption' + example: 1 + phone_number: + example: "0911111111" + type: string + reference_number: + type: string + verified: + example: true + type: boolean + type: object handlers.UpdateCashOutReq: properties: cashedOut: type: boolean type: object + handlers.UpdateTransactionVerifiedReq: + properties: + verified: + type: boolean + type: object + handlers.UpdateWalletActiveReq: + properties: + isActive: + type: boolean + type: object handlers.UserProfileRes: properties: created_at: @@ -225,6 +324,31 @@ definitions: updated_at: type: string type: object + handlers.WalletRes: + properties: + amount: + example: 100 + type: number + created_at: + type: string + id: + example: 1 + type: integer + is_active: + example: true + type: boolean + is_bettable: + example: true + type: boolean + is_withdraw: + example: true + type: boolean + updated_at: + type: string + user_id: + example: 1 + type: integer + type: object handlers.loginCustomerReq: properties: email: @@ -618,6 +742,123 @@ paths: summary: Get ticket by ID tags: - ticket + /transaction: + get: + consumes: + - application/json + description: Gets all the transactions + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.TransactionRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets all transactions + tags: + - transaction + post: + consumes: + - application/json + description: Creates a transaction + parameters: + - description: Creates transaction + in: body + name: createBet + required: true + schema: + $ref: '#/definitions/handlers.CreateTransactionReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransactionRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a transaction + tags: + - transaction + /transaction/{id}: + get: + consumes: + - application/json + description: Gets a single transaction by id + parameters: + - description: Transaction ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.TransactionRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets transaction by id + tags: + - transaction + patch: + consumes: + - application/json + description: Updates the cashed out field + parameters: + - description: Transaction ID + in: path + name: id + required: true + type: integer + - description: Updates Transaction Verification + in: body + name: updateCashOut + required: true + schema: + $ref: '#/definitions/handlers.UpdateTransactionVerifiedReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Updates the cashed out field + tags: + - transaction /user/checkPhoneEmailExist: post: consumes: @@ -793,6 +1034,94 @@ paths: summary: Send reset code tags: - user + /wallet: + get: + consumes: + - application/json + description: Retrieve all wallets + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.WalletRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all wallets + tags: + - wallet + /wallet/{id}: + get: + consumes: + - application/json + description: Retrieve wallet details by wallet ID + parameters: + - description: Wallet ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.WalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get wallet by ID + tags: + - wallet + patch: + consumes: + - application/json + description: Can activate and deactivate wallet + parameters: + - description: Wallet ID + in: path + name: id + required: true + type: integer + - description: Update Wallet Active + in: body + name: updateCashOut + required: true + schema: + $ref: '#/definitions/handlers.UpdateWalletActiveReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Activate and Deactivate Wallet + tags: + - wallet securityDefinitions: Bearer: in: header diff --git a/gen/db/models.go b/gen/db/models.go index e8e4c84..c446a64 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -82,8 +82,17 @@ type Ticket struct { type Transaction struct { ID int64 Amount int64 - TransactionType string - WalletID int64 + BranchID int64 + CashierID int64 + BetID int64 + PaymentOption int64 + FullName string + PhoneNumber string + BankCode string + BeneficiaryName string + AccountName string + AccountNumber string + ReferenceNumber string Verified bool CreatedAt pgtype.Timestamp UpdatedAt pgtype.Timestamp @@ -115,3 +124,13 @@ type Wallet struct { CreatedAt pgtype.Timestamp UpdatedAt pgtype.Timestamp } + +type WalletTransfer struct { + ID int64 + Amount int64 + WalletTransfer string + WalletID int64 + Verified bool + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} diff --git a/gen/db/transactions.sql.go b/gen/db/transactions.sql.go index d56aebc..31b535c 100644 --- a/gen/db/transactions.sql.go +++ b/gen/db/transactions.sql.go @@ -10,23 +10,54 @@ import ( ) const CreateTransaction = `-- name: CreateTransaction :one -INSERT INTO transactions (amount, transaction_type, wallet_id) VALUES ($1, $2, $3) RETURNING id, amount, transaction_type, wallet_id, verified, created_at, updated_at +INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at ` type CreateTransactionParams struct { Amount int64 - TransactionType string - WalletID int64 + BranchID int64 + CashierID int64 + BetID int64 + PaymentOption int64 + FullName string + PhoneNumber string + BankCode string + BeneficiaryName string + AccountName string + AccountNumber string + ReferenceNumber string } func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { - row := q.db.QueryRow(ctx, CreateTransaction, arg.Amount, arg.TransactionType, arg.WalletID) + row := q.db.QueryRow(ctx, CreateTransaction, + arg.Amount, + arg.BranchID, + arg.CashierID, + arg.BetID, + arg.PaymentOption, + arg.FullName, + arg.PhoneNumber, + arg.BankCode, + arg.BeneficiaryName, + arg.AccountName, + arg.AccountNumber, + arg.ReferenceNumber, + ) var i Transaction err := row.Scan( &i.ID, &i.Amount, - &i.TransactionType, - &i.WalletID, + &i.BranchID, + &i.CashierID, + &i.BetID, + &i.PaymentOption, + &i.FullName, + &i.PhoneNumber, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, &i.Verified, &i.CreatedAt, &i.UpdatedAt, @@ -35,7 +66,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa } const GetAllTransactions = `-- name: GetAllTransactions :many -SELECT id, amount, transaction_type, wallet_id, verified, created_at, updated_at FROM transactions +SELECT id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions ` func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) { @@ -50,8 +81,17 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) if err := rows.Scan( &i.ID, &i.Amount, - &i.TransactionType, - &i.WalletID, + &i.BranchID, + &i.CashierID, + &i.BetID, + &i.PaymentOption, + &i.FullName, + &i.PhoneNumber, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, &i.Verified, &i.CreatedAt, &i.UpdatedAt, @@ -67,7 +107,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) } const GetTransactionByID = `-- name: GetTransactionByID :one -SELECT id, amount, transaction_type, wallet_id, verified, created_at, updated_at FROM transactions WHERE id = $1 +SELECT id, amount, branch_id, cashier_id, bet_id, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1 ` func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) { @@ -76,8 +116,17 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction err := row.Scan( &i.ID, &i.Amount, - &i.TransactionType, - &i.WalletID, + &i.BranchID, + &i.CashierID, + &i.BetID, + &i.PaymentOption, + &i.FullName, + &i.PhoneNumber, + &i.BankCode, + &i.BeneficiaryName, + &i.AccountName, + &i.AccountNumber, + &i.ReferenceNumber, &i.Verified, &i.CreatedAt, &i.UpdatedAt, @@ -85,48 +134,16 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction return i, err } -const GetTransactionsByWallet = `-- name: GetTransactionsByWallet :many -SELECT id, amount, transaction_type, wallet_id, verified, created_at, updated_at FROM transactions WHERE wallet_id = $1 +const UpdateTransactionVerified = `-- name: UpdateTransactionVerified :exec +UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1 ` -func (q *Queries) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]Transaction, error) { - rows, err := q.db.Query(ctx, GetTransactionsByWallet, walletID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Transaction - for rows.Next() { - var i Transaction - if err := rows.Scan( - &i.ID, - &i.Amount, - &i.TransactionType, - &i.WalletID, - &i.Verified, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const UpdateTransferVerification = `-- name: UpdateTransferVerification :exec -UPDATE transactions SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 -` - -type UpdateTransferVerificationParams struct { - Verified bool +type UpdateTransactionVerifiedParams struct { ID int64 + Verified bool } -func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error { - _, err := q.db.Exec(ctx, UpdateTransferVerification, arg.Verified, arg.ID) +func (q *Queries) UpdateTransactionVerified(ctx context.Context, arg UpdateTransactionVerifiedParams) error { + _, err := q.db.Exec(ctx, UpdateTransactionVerified, arg.ID, arg.Verified) return err } diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go new file mode 100644 index 0000000..c29cec8 --- /dev/null +++ b/gen/db/transfer.sql.go @@ -0,0 +1,132 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: transfer.sql + +package dbgen + +import ( + "context" +) + +const CreateTransfer = `-- name: CreateTransfer :one +INSERT INTO wallet_transfer (amount, wallet_transfer, wallet_id) VALUES ($1, $2, $3) RETURNING id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at +` + +type CreateTransferParams struct { + Amount int64 + WalletTransfer string + WalletID int64 +} + +func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) (WalletTransfer, error) { + row := q.db.QueryRow(ctx, CreateTransfer, arg.Amount, arg.WalletTransfer, arg.WalletID) + var i WalletTransfer + err := row.Scan( + &i.ID, + &i.Amount, + &i.WalletTransfer, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetAllTransfers = `-- name: GetAllTransfers :many +SELECT id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at FROM wallet_transfer +` + +func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error) { + rows, err := q.db.Query(ctx, GetAllTransfers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WalletTransfer + for rows.Next() { + var i WalletTransfer + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.WalletTransfer, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetTransferByID = `-- name: GetTransferByID :one +SELECT id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at FROM wallet_transfer WHERE id = $1 +` + +func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer, error) { + row := q.db.QueryRow(ctx, GetTransferByID, id) + var i WalletTransfer + err := row.Scan( + &i.ID, + &i.Amount, + &i.WalletTransfer, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetTransfersByWallet = `-- name: GetTransfersByWallet :many +SELECT id, amount, wallet_transfer, wallet_id, verified, created_at, updated_at FROM wallet_transfer WHERE wallet_id = $1 +` + +func (q *Queries) GetTransfersByWallet(ctx context.Context, walletID int64) ([]WalletTransfer, error) { + rows, err := q.db.Query(ctx, GetTransfersByWallet, walletID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WalletTransfer + for rows.Next() { + var i WalletTransfer + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.WalletTransfer, + &i.WalletID, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const UpdateTransferVerification = `-- name: UpdateTransferVerification :exec +UPDATE wallet_transfer SET verified = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 +` + +type UpdateTransferVerificationParams struct { + Verified bool + ID int64 +} + +func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error { + _, err := q.db.Exec(ctx, UpdateTransferVerification, arg.Verified, arg.ID) + return err +} diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index c943cd2..de555d9 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -7,6 +7,8 @@ package dbgen import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) const CreateCustomerWallet = `-- name: CreateCustomerWallet :one @@ -41,23 +43,17 @@ func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWa } const CreateWallet = `-- name: CreateWallet :one -INSERT INTO wallets (balance, is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3, $4) RETURNING id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at +INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at ` type CreateWalletParams struct { - Balance int64 IsWithdraw bool IsBettable bool UserID int64 } func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wallet, error) { - row := q.db.QueryRow(ctx, CreateWallet, - arg.Balance, - arg.IsWithdraw, - arg.IsBettable, - arg.UserID, - ) + row := q.db.QueryRow(ctx, CreateWallet, arg.IsWithdraw, arg.IsBettable, arg.UserID) var i Wallet err := row.Scan( &i.ID, @@ -113,7 +109,10 @@ SELECT rw.id AS regular_id, rw.balance AS regular_balance, sw.id AS static_id, - sw.balance AS static_balance + sw.balance AS static_balance, + rw.updated_at as regular_updated_at, + sw.updated_at as static_updated_at, + cw.created_at FROM customer_wallets cw JOIN wallets rw ON cw.regular_wallet_id = rw.id JOIN wallets sw ON cw.static_wallet_id = sw.id @@ -126,13 +125,16 @@ type GetCustomerWalletParams struct { } type GetCustomerWalletRow struct { - ID int64 - CustomerID int64 - CompanyID int64 - RegularID int64 - RegularBalance int64 - StaticID int64 - StaticBalance int64 + ID int64 + CustomerID int64 + CompanyID int64 + RegularID int64 + RegularBalance int64 + StaticID int64 + StaticBalance int64 + RegularUpdatedAt pgtype.Timestamp + StaticUpdatedAt pgtype.Timestamp + CreatedAt pgtype.Timestamp } func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletParams) (GetCustomerWalletRow, error) { @@ -146,6 +148,9 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletPa &i.RegularBalance, &i.StaticID, &i.StaticBalance, + &i.RegularUpdatedAt, + &i.StaticUpdatedAt, + &i.CreatedAt, ) return i, err } @@ -170,6 +175,39 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { return i, err } +const GetWalletByUserID = `-- name: GetWalletByUserID :many +SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at FROM wallets WHERE user_id = $1 +` + +func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet, error) { + rows, err := q.db.Query(ctx, GetWalletByUserID, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Wallet + for rows.Next() { + var i Wallet + if err := rows.Scan( + &i.ID, + &i.Balance, + &i.IsWithdraw, + &i.IsBettable, + &i.UserID, + &i.IsActive, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const UpdateBalance = `-- name: UpdateBalance :exec UPDATE wallets SET balance = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 ` diff --git a/internal/domain/transaction.go b/internal/domain/transaction.go index 8fcd1dc..b4cdaf2 100644 --- a/internal/domain/transaction.go +++ b/internal/domain/transaction.go @@ -1,22 +1,44 @@ package domain -type TransactionType string +type PaymentOption int64 const ( - DEPOSIT TransactionType = "deposit" - WITHDRAW TransactionType = "withdraw" + CASH_TRANSACTION PaymentOption = iota + TELEBIRR_TRANSACTION + ARIFPAY_TRANSACTION + BANK ) type Transaction struct { - ID int64 - Amount Currency - Type TransactionType - Verified bool - WalletID int64 + ID int64 + Amount Currency + BranchID int64 + CashierID int64 + BetID int64 + PaymentOption PaymentOption + FullName string + PhoneNumber string + // Payment Details for bank + BankCode string + BeneficiaryName string + AccountName string + AccountNumber string + ReferenceNumber string + Verified bool } type CreateTransaction struct { - Amount Currency - Type TransactionType - WalletID int64 + Amount Currency + BranchID int64 + CashierID int64 + BetID int64 + PaymentOption PaymentOption + FullName string + PhoneNumber string + // Payment Details for bank + BankCode string + BeneficiaryName string + AccountName string + AccountNumber string + ReferenceNumber string } diff --git a/internal/domain/transfer.go b/internal/domain/transfer.go new file mode 100644 index 0000000..6c6210a --- /dev/null +++ b/internal/domain/transfer.go @@ -0,0 +1,23 @@ +package domain + +type TransferType string + +const ( + DEPOSIT TransferType = "deposit" + WITHDRAW TransferType = "withdraw" +) + +type Transfer struct { + ID int64 + Amount Currency + Verified bool + WalletID int64 + Type TransferType +} + +type CreateTransfer struct { + Amount Currency + Verified bool + WalletID int64 + Type TransferType +} diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index f738104..ff92ef6 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -1,5 +1,7 @@ package domain +import "time" + type Wallet struct { ID int64 Balance Currency @@ -7,27 +9,31 @@ type Wallet struct { IsBettable bool IsActive bool UserID int64 + UpdatedAt time.Time + CreatedAt time.Time } type CustomerWallet struct { - ID int64 - RegularID int64 - StaticID int64 - CustomerID int64 - CompanyID int64 + ID int64 + RegularID int64 + StaticID int64 + CustomerID int64 + CompanyID int64 } type GetCustomerWallet struct { - ID int64 - RegularID int64 - RegularBalance Currency - StaticID int64 - StaticBalance Currency - CustomerID int64 - CompanyID int64 + ID int64 + RegularID int64 + RegularBalance Currency + StaticID int64 + StaticBalance Currency + CustomerID int64 + CompanyID int64 + RegularUpdatedAt time.Time + StaticUpdatedAt time.Time + CreatedAt time.Time } type CreateWallet struct { - Balance Currency IsWithdraw bool IsBettable bool UserID int64 diff --git a/internal/repository/bet.go b/internal/repository/bet.go index b3c4cc3..c4d0362 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -73,7 +73,7 @@ func (s *Store) GetAllBets(ctx context.Context) ([]domain.Bet, error) { return nil, err } - var result []domain.Bet + var result []domain.Bet = make([]domain.Bet, len(bets)) for _, bet := range bets { result = append(result, convertDBBet(bet)) } diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index b2ba5b7..d6918d3 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -51,7 +51,7 @@ func (s *Store) GetAllTickets(ctx context.Context) ([]domain.Ticket, error) { return nil, err } - var result []domain.Ticket + var result []domain.Ticket = make([]domain.Ticket, len(tickets)) for _, ticket := range tickets { result = append(result, convertDBTicket(ticket)) } diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go index bef1090..87f04f7 100644 --- a/internal/repository/transaction.go +++ b/internal/repository/transaction.go @@ -9,68 +9,74 @@ import ( func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { return domain.Transaction{ - ID: transaction.ID, - Amount: domain.Currency(transaction.Amount), - Type: domain.TransactionType(transaction.TransactionType), - Verified: transaction.Verified, - WalletID: transaction.WalletID, + Amount: domain.Currency(transaction.Amount), + BranchID: transaction.BranchID, + CashierID: transaction.CashierID, + BetID: transaction.BetID, + PaymentOption: domain.PaymentOption(transaction.PaymentOption), + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: transaction.BankCode, + BeneficiaryName: transaction.BeneficiaryName, + AccountName: transaction.AccountName, + AccountNumber: transaction.AccountNumber, + ReferenceNumber: transaction.ReferenceNumber, } } func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { return dbgen.CreateTransactionParams{ Amount: int64(transaction.Amount), - TransactionType: string(transaction.Type), - WalletID: transaction.WalletID, + BranchID: transaction.BranchID, + CashierID: transaction.CashierID, + BetID: transaction.BetID, + PaymentOption: int64(transaction.PaymentOption), + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: transaction.BankCode, + BeneficiaryName: transaction.BeneficiaryName, + AccountName: transaction.AccountName, + AccountNumber: transaction.AccountNumber, + ReferenceNumber: transaction.ReferenceNumber, } } func (s *Store) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { + newTransaction, err := s.queries.CreateTransaction(ctx, convertCreateTransaction(transaction)) if err != nil { return domain.Transaction{}, err } - return convertDBTransaction(newTransaction), nil + return convertDBTransaction(newTransaction), err } -func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { - transactions, err := s.queries.GetAllTransactions(ctx) +func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { + transaction, err := s.queries.GetTransactionByID(ctx, id) if err != nil { - return nil, err - } - var result []domain.Transaction - for _, transaction := range transactions { - result = append(result, convertDBTransaction(transaction)) - } - return result, nil -} -func (s *Store) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) { - transactions, err := s.queries.GetTransactionsByWallet(ctx, walletID) - if err != nil { - return nil, err + return domain.Transaction{}, err } - var result []domain.Transaction - for _, transaction := range transactions { - result = append(result, convertDBTransaction(transaction)) - } - return result, nil -} - - -func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error){ - transaction, err := s.queries.GetTransactionByID(ctx, id); - if err != nil { - return domain.Transaction{}, nil - } return convertDBTransaction(transaction), nil } -func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error{ - err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ - ID: id, +func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { + transaction, err := s.queries.GetAllTransactions(ctx) + + if err != nil { + return nil, err + } + + var result []domain.Transaction = make([]domain.Transaction, len(transaction)) + for _, ticket := range transaction { + result = append(result, convertDBTransaction(ticket)) + } + return result, nil +} + +func (s *Store) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { + err := s.queries.UpdateTransactionVerified(ctx, dbgen.UpdateTransactionVerifiedParams{ + ID: id, Verified: verified, }) - return err } diff --git a/internal/repository/transfer.go b/internal/repository/transfer.go new file mode 100644 index 0000000..d39178b --- /dev/null +++ b/internal/repository/transfer.go @@ -0,0 +1,77 @@ +package repository + +// import ( +// "context" + +// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" +// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +// ) + +// func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { +// return domain.Transaction{ +// ID: transaction.ID, +// Amount: domain.Currency(transaction.Amount), +// Type: domain.TransactionType(transaction.TransactionType), +// Verified: transaction.Verified, +// WalletID: transaction.WalletID, +// } +// } + +// func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { +// return dbgen.CreateTransactionParams{ +// Amount: int64(transaction.Amount), +// TransactionType: string(transaction.Type), +// WalletID: transaction.WalletID, +// } +// } + +// func (s *Store) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { +// newTransaction, err := s.queries.CreateTransaction(ctx, convertCreateTransaction(transaction)) +// if err != nil { +// return domain.Transaction{}, err +// } +// return convertDBTransaction(newTransaction), nil +// } + +// func (s *Store) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { +// transactions, err := s.queries.GetAllTransactions(ctx) +// if err != nil { +// return nil, err +// } +// var result []domain.Transaction = make([]domain.Transaction, len(transactions)) + +// for _, transaction := range transactions { +// result = append(result, convertDBTransaction(transaction)) +// } +// return result, nil +// } +// func (s *Store) GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) { +// transactions, err := s.queries.GetTransactionsByWallet(ctx, walletID) +// if err != nil { +// return nil, err +// } + +// var result []domain.Transaction = make([]domain.Transaction, len(transactions)) + +// for _, transaction := range transactions { +// result = append(result, convertDBTransaction(transaction)) +// } +// return result, nil +// } + +// func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { +// transaction, err := s.queries.GetTransactionByID(ctx, id) +// if err != nil { +// return domain.Transaction{}, nil +// } +// return convertDBTransaction(transaction), nil +// } + +// func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { +// err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ +// ID: id, +// Verified: verified, +// }) + +// return err +// } diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 13514fe..87f88ac 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -15,12 +15,13 @@ func convertDBWallet(wallet dbgen.Wallet) domain.Wallet { IsBettable: wallet.IsBettable, IsActive: wallet.IsActive, UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt.Time, + CreatedAt: wallet.CreatedAt.Time, } } func convertCreateWallet(wallet domain.CreateWallet) dbgen.CreateWalletParams { return dbgen.CreateWalletParams{ - Balance: int64(wallet.Balance), IsWithdraw: wallet.IsWithdraw, IsBettable: wallet.IsBettable, UserID: wallet.UserID, @@ -47,13 +48,16 @@ func convertCreateCustomerWallet(customerWallet domain.CreateCustomerWallet) dbg func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domain.GetCustomerWallet { return domain.GetCustomerWallet{ - ID: customerWallet.ID, - RegularID: customerWallet.RegularID, - RegularBalance: domain.Currency(customerWallet.RegularBalance), - StaticID: customerWallet.StaticID, - StaticBalance: domain.Currency(customerWallet.StaticBalance), - CustomerID: customerWallet.CustomerID, - CompanyID: customerWallet.CompanyID, + ID: customerWallet.ID, + RegularID: customerWallet.RegularID, + RegularBalance: domain.Currency(customerWallet.RegularBalance), + StaticID: customerWallet.StaticID, + StaticBalance: domain.Currency(customerWallet.StaticBalance), + CustomerID: customerWallet.CustomerID, + CompanyID: customerWallet.CompanyID, + RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time, + StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time, + CreatedAt: customerWallet.CreatedAt.Time, } } @@ -66,6 +70,7 @@ func (s *Store) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (d } func (s *Store) CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error) { + newCustomerWallet, err := s.queries.CreateCustomerWallet(ctx, convertCreateCustomerWallet(customerWallet)) if err != nil { return domain.CustomerWallet{}, err @@ -88,7 +93,22 @@ func (s *Store) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { return nil, err } - var result []domain.Wallet + var result []domain.Wallet = make([]domain.Wallet, len(wallets)) + + for _, wallet := range wallets { + result = append(result, convertDBWallet(wallet)) + } + return result, nil +} + +func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wallet, error) { + wallets, err := s.queries.GetWalletByUserID(ctx, userID) + if err != nil { + return nil, err + } + + var result []domain.Wallet = make([]domain.Wallet, len(wallets)) + for _, wallet := range wallets { result = append(result, convertDBWallet(wallet)) } diff --git a/internal/services/transaction/port.go b/internal/services/transaction/port.go index 9d47cb4..27b9f5a 100644 --- a/internal/services/transaction/port.go +++ b/internal/services/transaction/port.go @@ -8,8 +8,7 @@ import ( type TransactionStore interface { CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) - GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) - GetTransactionsByWallet(ctx context.Context, walletID int64) ([]domain.Transaction, error) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) - UpdateTransferVerification(ctx context.Context, id int64, verified bool) error + GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) + UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error } diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index 3d57afd..31ca58e 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -19,15 +19,13 @@ func NewService(transactionStore TransactionStore) *Service { func (s *Service) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { return s.transactionStore.CreateTransaction(ctx, transaction) } - -func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { - return s.transactionStore.GetAllTransactions(ctx) -} - func (s *Service) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { return s.transactionStore.GetTransactionByID(ctx, id) } - -func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { - return s.transactionStore.UpdateTransferVerification(ctx, id, verified) +func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) { + return s.transactionStore.GetAllTransactions(ctx) +} +func (s *Service) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { + + return s.transactionStore.UpdateTransactionVerified(ctx, id, verified) } diff --git a/internal/services/transfer/chapa.go b/internal/services/transfer/chapa.go new file mode 100644 index 0000000..3e884d3 --- /dev/null +++ b/internal/services/transfer/chapa.go @@ -0,0 +1 @@ +package transfer diff --git a/internal/services/transfer/port.go b/internal/services/transfer/port.go new file mode 100644 index 0000000..213d65a --- /dev/null +++ b/internal/services/transfer/port.go @@ -0,0 +1,15 @@ +package transfer + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type TransferStore interface { + CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) + GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) + GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) + GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) + UpdateTransferVerification(ctx context.Context, id int64, verified bool) error +} diff --git a/internal/services/transfer/service.go b/internal/services/transfer/service.go new file mode 100644 index 0000000..c628c42 --- /dev/null +++ b/internal/services/transfer/service.go @@ -0,0 +1,33 @@ +package transfer + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + transferStore TransferStore +} + +func NewService(transferStore TransferStore) *Service { + return &Service{ + transferStore: transferStore, + } +} + +func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { + return s.transferStore.CreateTransfer(ctx, transfer) +} + +func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { + return s.transferStore.GetAllTransfers(ctx) +} + +func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { + return s.transferStore.GetTransferByID(ctx, id) +} + +func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { + return s.transferStore.UpdateTransferVerification(ctx, id, verified) +} diff --git a/internal/services/user/service.go b/internal/services/user/service.go index cfa93fd..17a7820 100644 --- a/internal/services/user/service.go +++ b/internal/services/user/service.go @@ -13,6 +13,7 @@ type Service struct { otpStore OtpStore smsGateway SmsGateway emailGateway EmailGateway + } func NewService( diff --git a/internal/services/wallet/port.go b/internal/services/wallet/port.go index 3ac6a12..0e369bd 100644 --- a/internal/services/wallet/port.go +++ b/internal/services/wallet/port.go @@ -11,6 +11,7 @@ type WalletStore interface { CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) + GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error UpdateWalletActive(ctx context.Context, id int64, isActive bool) error diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go index 38d2031..1cb9ebe 100644 --- a/internal/services/wallet/service.go +++ b/internal/services/wallet/service.go @@ -2,6 +2,7 @@ package wallet import ( "context" + "errors" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) @@ -16,12 +17,42 @@ func NewService(walletStore WalletStore) *Service { } } +var ( + ErrBalanceInsufficient = errors.New("wallet balance is insufficient") +) + func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { return s.walletStore.CreateWallet(ctx, wallet) } -func (s *Service) CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error) { - return s.walletStore.CreateCustomerWallet(ctx, customerWallet) +func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.CustomerWallet, error) { + + regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ + IsWithdraw: true, + IsBettable: true, + UserID: customerID, + }) + + if err != nil { + return domain.CustomerWallet{}, err + } + + staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ + IsWithdraw: false, + IsBettable: true, + UserID: customerID, + }) + + if err != nil { + return domain.CustomerWallet{}, err + } + + return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{ + CustomerID: customerID, + CompanyID: companyID, + RegularWalletID: regularWallet.ID, + StaticWalletID: staticWallet.ID, + }) } func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { @@ -32,6 +63,10 @@ func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { return s.walletStore.GetAllWallets(ctx) } +func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) { + return s.walletStore.GetWalletsByUser(ctx, id) +} + func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64, companyID int64) (domain.GetCustomerWallet, error) { return s.walletStore.GetCustomerWallet(ctx, customerID, companyID) } @@ -40,6 +75,28 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu return s.walletStore.UpdateBalance(ctx, id, balance) } +func (s *Service) Add(ctx context.Context, id int64, amount domain.Currency) error { + wallet, err := s.GetWalletByID(ctx, id) + if err != nil { + return err + } + + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) +} + +func (s *Service) Deduct(ctx context.Context, id int64, amount domain.Currency) error { + wallet, err := s.GetWalletByID(ctx, id) + if err != nil { + return err + } + + if wallet.Balance < amount { + return ErrBalanceInsufficient + } + + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) +} + func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { return s.walletStore.UpdateWalletActive(ctx, id, isActive) } diff --git a/internal/web_server/app.go b/internal/web_server/app.go index 7f5b575..bdf1b33 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -7,7 +7,9 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" @@ -17,16 +19,18 @@ import ( ) type App struct { - fiber *fiber.App - logger *slog.Logger + fiber *fiber.App + logger *slog.Logger NotidicationStore notificationservice.NotificationStore - port int - authSvc *authentication.Service - userSvc *user.Service - ticketSvc *ticket.Service - betSvc *bet.Service - validator *customvalidator.CustomValidator - JwtConfig jwtutil.JwtConfig + port int + authSvc *authentication.Service + userSvc *user.Service + ticketSvc *ticket.Service + betSvc *bet.Service + walletSvc *wallet.Service + transactionSvc *transaction.Service + validator *customvalidator.CustomValidator + JwtConfig jwtutil.JwtConfig Logger *slog.Logger } @@ -38,7 +42,10 @@ func NewApp( userSvc *user.Service, ticketSvc *ticket.Service, betSvc *bet.Service, - notidicationStore notificationservice.NotificationStore) *App { + walletSvc *wallet.Service, + transactionSvc *transaction.Service, + notidicationStore notificationservice.NotificationStore, +) *App { app := fiber.New(fiber.Config{ CaseSensitive: true, DisableHeaderNormalizing: true, @@ -46,15 +53,17 @@ func NewApp( JSONDecoder: sonic.Unmarshal, }) s := &App{ - fiber: app, - port: port, - authSvc: authSvc, - validator: validator, - logger: logger, - JwtConfig: JwtConfig, - userSvc: userSvc, - ticketSvc: ticketSvc, - betSvc: betSvc, + fiber: app, + port: port, + authSvc: authSvc, + validator: validator, + logger: logger, + JwtConfig: JwtConfig, + userSvc: userSvc, + ticketSvc: ticketSvc, + betSvc: betSvc, + walletSvc: walletSvc, + transactionSvc: transactionSvc, NotidicationStore: notidicationStore, Logger: logger, } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index fb49a6f..9d83592 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -136,7 +136,7 @@ func GetAllBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil) } - var res []BetRes + var res []BetRes = make([]BetRes, len(bets)) for _, bet := range bets { res = append(res, convertBet(bet)) } diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index 696b738..68bba4b 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -136,7 +136,7 @@ func GetAllTickets(logger *slog.Logger, ticketSvc *ticket.Service, return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve tickets", err, nil) } - var res []TicketRes + var res []TicketRes = make([]TicketRes, len(tickets)) for _, ticket := range tickets { res = append(res, TicketRes{ diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go new file mode 100644 index 0000000..1d143c7 --- /dev/null +++ b/internal/web_server/handlers/transaction_handler.go @@ -0,0 +1,232 @@ +package handlers + +import ( + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type TransactionRes struct { + ID int64 `json:"id" example:"1"` + Amount float32 `json:"amount" example:"100.0"` + BranchID int64 `json:"branch_id" example:"1"` + CashierID int64 `json:"cashier_id" example:"1"` + BetID int64 `json:"bet_id" example:"1"` + + PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` + FullName string `json:"full_name" example:"John Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` + Verified bool `json:"verified" example:"true"` +} + +type CreateTransactionReq struct { + Amount float32 `json:"amount" example:"100.0"` + BranchID int64 `json:"branch_id" example:"1"` + CashierID int64 `json:"cashier_id" example:"1"` + BetID int64 `json:"bet_id" example:"1"` + PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` + FullName string `json:"full_name" example:"John Smith"` + PhoneNumber string `json:"phone_number" example:"0911111111"` + // Payment Details for bank + BankCode string `json:"bank_code"` + BeneficiaryName string `json:"beneficiary_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + ReferenceNumber string `json:"reference_number"` +} + +func convertTransaction(transaction domain.Transaction) TransactionRes { + return TransactionRes{ + ID: transaction.ID, + Amount: transaction.Amount.Float64(), + BranchID: transaction.BranchID, + CashierID: transaction.CashierID, + BetID: transaction.BetID, + PaymentOption: transaction.PaymentOption, + FullName: transaction.FullName, + PhoneNumber: transaction.PhoneNumber, + BankCode: transaction.BankCode, + BeneficiaryName: transaction.BeneficiaryName, + AccountName: transaction.AccountName, + AccountNumber: transaction.AccountNumber, + ReferenceNumber: transaction.ReferenceNumber, + Verified: transaction.Verified, + } +} + +// CreateTransaction godoc +// @Summary Create a transaction +// @Description Creates a transaction +// @Tags transaction +// @Accept json +// @Produce json +// @Param createBet body CreateTransactionReq true "Creates transaction" +// @Success 200 {object} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transaction [post] +func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + var req CreateTransactionReq + if err := c.BodyParser(&req); err != nil { + logger.Error("CreateTransactionReq failed", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request", + }) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + return nil + } + + transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ + Amount: domain.Currency(req.Amount), + BranchID: req.BranchID, + CashierID: req.CashierID, + BetID: req.BetID, + PaymentOption: domain.PaymentOption(req.PaymentOption), + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + BankCode: req.BankCode, + BeneficiaryName: req.BeneficiaryName, + AccountName: req.AccountName, + AccountNumber: req.AccountNumber, + ReferenceNumber: req.ReferenceNumber, + }) + + if err != nil { + logger.Error("CreateTransactionReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) + } + + res := convertTransaction(transaction) + + return response.WriteJSON(c, fiber.StatusOK, "Transaction Created", res, nil) + } +} + +// GetAllTransactions godoc +// @Summary Gets all transactions +// @Description Gets all the transactions +// @Tags transaction +// @Accept json +// @Produce json +// @Success 200 {array} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transaction [get] +func GetAllTransactions(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + transactions, err := transactionSvc.GetAllTransactions(c.Context()) + + if err != nil { + logger.Error("Failed to get transaction", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil) + } + + var res []TransactionRes = make([]TransactionRes, len(transactions)) + for _, transaction := range transactions { + res = append(res, convertTransaction(transaction)) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Transactions Retrieved", res, nil) + } +} + +// GetTransactionByID godoc +// @Summary Gets transaction by id +// @Description Gets a single transaction by id +// @Tags transaction +// @Accept json +// @Produce json +// @Param id path int true "Transaction ID" +// @Success 200 {object} TransactionRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transaction/{id} [get] +func GetTransactionByID(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + transactionID := c.Params("id") + id, err := strconv.ParseInt(transactionID, 10, 64) + + if err != nil { + logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil) + } + + transaction, err := transactionSvc.GetTransactionByID(c.Context(), id) + + if err != nil { + logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil) + } + + res := convertTransaction(transaction) + + return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) + } +} + +type UpdateTransactionVerifiedReq struct { + Verified bool +} + +// UpdateTransactionVerified godoc +// @Summary Updates the cashed out field +// @Description Updates the cashed out field +// @Tags transaction +// @Accept json +// @Produce json +// @Param id path int true "Transaction ID" +// @Param updateCashOut body UpdateTransactionVerifiedReq true "Updates Transaction Verification" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /transaction/{id} [patch] +func UpdateTransactionVerified(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + transactionID := c.Params("id") + id, err := strconv.ParseInt(transactionID, 10, 64) + + if err != nil { + logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil) + } + + var req UpdateTransactionVerifiedReq + if err := c.BodyParser(&req); err != nil { + logger.Error("UpdateTransactionVerifiedReq failed", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request", + }) + } + + valErrs, ok := validator.Validate(c, req) + if !ok { + response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + return nil + } + + err = transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified) + + if err != nil { + logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update transaction verification", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil) + + } +} diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 139eb09..20e971b 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -7,6 +7,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" @@ -137,7 +138,7 @@ type RegisterUserReq struct { // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /user/register [post] -func RegisterUser(logger *slog.Logger, userSvc *user.Service, +func RegisterUser(logger *slog.Logger, userSvc *user.Service, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req RegisterUserReq @@ -170,7 +171,8 @@ func RegisterUser(logger *slog.Logger, userSvc *user.Service, }) } user.OtpMedium = medium - if _, err := userSvc.RegisterUser(c.Context(), user); err != nil { + newUser, err := userSvc.RegisterUser(c.Context(), user) + if err != nil { if errors.Is(err, domain.ErrOtpAlreadyUsed) { return response.WriteJSON(c, fiber.StatusBadRequest, "Otp already used", nil, nil) } @@ -188,6 +190,15 @@ func RegisterUser(logger *slog.Logger, userSvc *user.Service, "error": "Internal server error", }) } + + // TODO: Integrate company when we move to multi-vendor system + _, err = walletSvc.CreateCustomerWallet(c.Context(), newUser.ID, 0) + + if err != nil { + logger.Error("RegisterUser failed", "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create customer wallet for user", err, nil) + } + return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil) } } diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go new file mode 100644 index 0000000..4f9c16b --- /dev/null +++ b/internal/web_server/handlers/wallet_handler.go @@ -0,0 +1,148 @@ +package handlers + +import ( + "log/slog" + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" + "github.com/gofiber/fiber/v2" +) + +type WalletRes struct { + ID int64 `json:"id" example:"1"` + Balance float32 `json:"amount" example:"100.0"` + IsWithdraw bool `json:"is_withdraw" example:"true"` + IsBettable bool `json:"is_bettable" example:"true"` + IsActive bool `json:"is_active" example:"true"` + UserID int64 `json:"user_id" example:"1"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +// GetWalletByID godoc +// @Summary Get wallet by ID +// @Description Retrieve wallet details by wallet ID +// @Tags wallet +// @Accept json +// @Produce json +// @Param id path int true "Wallet ID" +// @Success 200 {object} WalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /wallet/{id} [get] +func GetWalletByID(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + walletID := c.Params("id") + + id, err := strconv.ParseInt(walletID, 10, 64) + + if err != nil { + logger.Error("Invalid wallet ID", "walletID", walletID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + + wallet, err := walletSvc.GetWalletByID(c.Context(), id) + if err != nil { + logger.Error("Failed to get wallet by ID", "walletID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil) + } + + res := WalletRes{ + ID: wallet.ID, + Balance: wallet.Balance.Float64(), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt, + CreatedAt: wallet.CreatedAt, + } + + return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) + } +} + +// GetAllWallets godoc +// @Summary Get all wallets +// @Description Retrieve all wallets +// @Tags wallet +// @Accept json +// @Produce json +// @Success 200 {array} WalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /wallet [get] +func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + wallets, err := walletSvc.GetAllWallets(c.Context()) + + if err != nil { + logger.Error("Failed to get wallets", "error", err) + } + + var res []WalletRes = make([]WalletRes, len(wallets)) + + for _, wallet := range wallets { + res = append(res, WalletRes{ + ID: wallet.ID, + Balance: wallet.Balance.Float64(), + IsWithdraw: wallet.IsWithdraw, + IsBettable: wallet.IsBettable, + IsActive: wallet.IsActive, + UserID: wallet.UserID, + UpdatedAt: wallet.UpdatedAt, + CreatedAt: wallet.CreatedAt, + }) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil) + } +} + +type UpdateWalletActiveReq struct { + IsActive bool +} + +// UpdateWalletActive godoc +// @Summary Activate and Deactivate Wallet +// @Description Can activate and deactivate wallet +// @Tags wallet +// @Accept json +// @Produce json +// @Param id path int true "Wallet ID" +// @Param updateCashOut body UpdateWalletActiveReq true "Update Wallet Active" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /wallet/{id} [patch] +func UpdateWalletActive(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + walletID := c.Params("id") + id, err := strconv.ParseInt(walletID, 10, 64) + + if err != nil { + logger.Error("Invalid bet ID", "walletID", walletID, "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil) + } + + var req UpdateWalletActiveReq + if err := c.BodyParser(&req); err != nil { + logger.Error("UpdateWalletActiveReq failed", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request", + }) + } + + err = walletSvc.UpdateWalletActive(c.Context(), id, req.IsActive) + + if err != nil { + logger.Error("Failed to update", "walletID", id, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update wallet", err, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Wallet successfully updated", nil, nil) + } +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 2456d3b..05d3fa3 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -22,7 +22,7 @@ func (a *App) initAppRoutes() { }) a.fiber.Post("/user/resetPassword", handlers.ResetPassword(a.logger, a.userSvc, a.validator)) a.fiber.Post("/user/sendResetCode", handlers.SendResetCode(a.logger, a.userSvc, a.validator)) - a.fiber.Post("/user/register", handlers.RegisterUser(a.logger, a.userSvc, a.validator)) + a.fiber.Post("/user/register", handlers.RegisterUser(a.logger, a.userSvc, a.walletSvc, a.validator)) a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator)) a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator)) a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc)) @@ -37,10 +37,21 @@ func (a *App) initAppRoutes() { // Bet a.fiber.Post("/bet", handlers.CreateBet(a.logger, a.betSvc, a.validator)) a.fiber.Get("/bet", handlers.GetAllBet(a.logger, a.betSvc, a.validator)) - a.fiber.Get("/bet/:id", handlers.GetAllBet(a.logger, a.betSvc, a.validator)) + a.fiber.Get("/bet/:id", handlers.GetBetByID(a.logger, a.betSvc, a.validator)) a.fiber.Patch("/bet/:id", handlers.UpdateCashOut(a.logger, a.betSvc, a.validator)) a.fiber.Delete("/bet/:id", handlers.DeleteBet(a.logger, a.betSvc, a.validator)) + // Wallet + a.fiber.Get("/wallet", handlers.GetAllWallets(a.logger, a.walletSvc, a.validator)) + a.fiber.Get("/wallet/:id", handlers.GetWalletByID(a.logger, a.walletSvc, a.validator)) + a.fiber.Put("/wallet/:id", handlers.UpdateWalletActive(a.logger, a.walletSvc, a.validator)) + + // Transactions /transactions + a.fiber.Post("/transaction", handlers.CreateTransaction(a.logger, a.transactionSvc, a.validator)) + a.fiber.Get("/transaction", handlers.GetAllTransactions(a.logger, a.transactionSvc, a.validator)) + a.fiber.Get("/transaction/:id", handlers.GetTransactionByID(a.logger, a.transactionSvc, a.validator)) + a.fiber.Patch("/transaction/:id", handlers.UpdateTransactionVerified(a.logger, a.transactionSvc, a.validator)) + a.fiber.Get("/ws/:recipientID", handlers.ConnectSocket(*a.logger, a.NotidicationStore, a.validator)) } From ddc4f8bc54b63973132c10988bf08bc5e83b5242 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Fri, 4 Apr 2025 23:29:08 +0300 Subject: [PATCH 3/3] added customer wallet --- docs/docs.go | 91 +++++++++++++++++++ docs/swagger.json | 91 +++++++++++++++++++ docs/swagger.yaml | 61 +++++++++++++ internal/web_server/handlers/user.go | 2 +- .../web_server/handlers/wallet_handler.go | 64 +++++++++++++ internal/web_server/routes.go | 26 ++++-- 6 files changed, 328 insertions(+), 7 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index f8d47cd..36464a6 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -972,6 +972,55 @@ const docTemplate = `{ } } }, + "/user/wallet": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Retrieve customer wallet details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get customer wallet", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "company_id", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomerWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/wallet": { "get": { "description": "Retrieve all wallets", @@ -1352,6 +1401,48 @@ const docTemplate = `{ } } }, + "handlers.CustomerWalletRes": { + "type": "object", + "properties": { + "company_id": { + "type": "integer", + "example": 1 + }, + "created_at": { + "type": "string" + }, + "customer_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "regular_balance": { + "type": "number", + "example": 100 + }, + "regular_id": { + "type": "integer", + "example": 1 + }, + "regular_updated_at": { + "type": "string" + }, + "static_balance": { + "type": "number", + "example": 100 + }, + "static_id": { + "type": "integer", + "example": 1 + }, + "static_updated_at": { + "type": "string" + } + } + }, "handlers.RegisterCodeReq": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 3a34879..cc51adb 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -964,6 +964,55 @@ } } }, + "/user/wallet": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Retrieve customer wallet details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "wallet" + ], + "summary": "Get customer wallet", + "parameters": [ + { + "type": "integer", + "description": "Company ID", + "name": "company_id", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomerWalletRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/wallet": { "get": { "description": "Retrieve all wallets", @@ -1344,6 +1393,48 @@ } } }, + "handlers.CustomerWalletRes": { + "type": "object", + "properties": { + "company_id": { + "type": "integer", + "example": 1 + }, + "created_at": { + "type": "string" + }, + "customer_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "regular_balance": { + "type": "number", + "example": 100 + }, + "regular_id": { + "type": "integer", + "example": 1 + }, + "regular_updated_at": { + "type": "string" + }, + "static_balance": { + "type": "number", + "example": 100 + }, + "static_id": { + "type": "integer", + "example": 1 + }, + "static_updated_at": { + "type": "string" + } + } + }, "handlers.RegisterCodeReq": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d9d0d27..9b8b3d4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -171,6 +171,36 @@ definitions: reference_number: type: string type: object + handlers.CustomerWalletRes: + properties: + company_id: + example: 1 + type: integer + created_at: + type: string + customer_id: + example: 1 + type: integer + id: + example: 1 + type: integer + regular_balance: + example: 100 + type: number + regular_id: + example: 1 + type: integer + regular_updated_at: + type: string + static_balance: + example: 100 + type: number + static_id: + example: 1 + type: integer + static_updated_at: + type: string + type: object handlers.RegisterCodeReq: properties: email: @@ -1034,6 +1064,37 @@ paths: summary: Send reset code tags: - user + /user/wallet: + get: + consumes: + - application/json + description: Retrieve customer wallet details + parameters: + - description: Company ID + in: header + name: company_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CustomerWalletRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - Bearer: [] + summary: Get customer wallet + tags: + - wallet /wallet: get: consumes: diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 20e971b..665a1ee 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -195,7 +195,7 @@ func RegisterUser(logger *slog.Logger, userSvc *user.Service, walletSvc *wallet. _, err = walletSvc.CreateCustomerWallet(c.Context(), newUser.ID, 0) if err != nil { - logger.Error("RegisterUser failed", "error", err) + logger.Error("CreateCustomerWallet failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create customer wallet for user", err, nil) } diff --git a/internal/web_server/handlers/wallet_handler.go b/internal/web_server/handlers/wallet_handler.go index 4f9c16b..3f6cb67 100644 --- a/internal/web_server/handlers/wallet_handler.go +++ b/internal/web_server/handlers/wallet_handler.go @@ -5,6 +5,7 @@ import ( "strconv" "time" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" @@ -146,3 +147,66 @@ func UpdateWalletActive(logger *slog.Logger, walletSvc *wallet.Service, validato return response.WriteJSON(c, fiber.StatusOK, "Wallet successfully updated", nil, nil) } } + +type CustomerWalletRes struct { + ID int64 `json:"id" example:"1"` + RegularID int64 `json:"regular_id" example:"1"` + RegularBalance float32 `json:"regular_balance" example:"100.0"` + StaticID int64 `json:"static_id" example:"1"` + StaticBalance float32 `json:"static_balance" example:"100.0"` + CustomerID int64 `json:"customer_id" example:"1"` + CompanyID int64 `json:"company_id" example:"1"` + RegularUpdatedAt time.Time `json:"regular_updated_at"` + StaticUpdatedAt time.Time `json:"static_updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +// GetCustomerWallet godoc +// @Summary Get customer wallet +// @Description Retrieve customer wallet details +// @Tags wallet +// @Accept json +// @Produce json +// @Param company_id header int true "Company ID" +// @Security Bearer +// @Success 200 {object} CustomerWalletRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /user/wallet [get] +func GetCustomerWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler { + return func(c *fiber.Ctx) error { + + userId := c.Locals("user_id").(int64) + role := string(c.Locals("role").(domain.Role)) + + companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64) + if err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Invalid company_id") + } + logger.Info("Company ID: " + strconv.FormatInt(companyID, 10)) + + if role != string(domain.RoleCustomer) { + logger.Error("Unauthorized access", "userId", userId, "role", role) + return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil) + } + wallet, err := walletSvc.GetCustomerWallet(c.Context(), userId, companyID) + if err != nil { + logger.Error("Failed to get customer wallet", "userId", userId, "error", err) + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil) + } + res := CustomerWalletRes{ + ID: wallet.ID, + RegularID: wallet.RegularID, + RegularBalance: wallet.RegularBalance.Float64(), + StaticID: wallet.StaticID, + StaticBalance: wallet.StaticBalance.Float64(), + CustomerID: wallet.CustomerID, + CompanyID: wallet.CompanyID, + RegularUpdatedAt: wallet.RegularUpdatedAt, + StaticUpdatedAt: wallet.StaticUpdatedAt, + CreatedAt: wallet.CreatedAt, + } + return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) + + } +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 05d3fa3..3f31fd9 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -1,7 +1,11 @@ package httpserver import ( + "fmt" + "strconv" + _ "github.com/SamuelTariku/FortuneBet-Backend/docs" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/handlers" "github.com/gofiber/fiber/v2" fiberSwagger "github.com/swaggo/fiber-swagger" @@ -12,12 +16,19 @@ func (a *App) initAppRoutes() { a.fiber.Post("/auth/refresh", a.authMiddleware, handlers.RefreshToken(a.logger, a.authSvc, a.validator, a.JwtConfig)) a.fiber.Post("/auth/logout", a.authMiddleware, handlers.LogOutCustomer(a.logger, a.authSvc, a.validator)) a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error { - userId := c.Locals("user_id") - role := c.Locals("role") - refreshToken := c.Locals("refresh_token") - a.logger.Info("User ID: " + userId.(string)) - a.logger.Info("Role: " + role.(string)) - a.logger.Info("Refresh Token: " + refreshToken.(string)) + userId := c.Locals("user_id").(int64) + role := string(c.Locals("role").(domain.Role)) + refreshToken := (c.Locals("refresh_token").(string)) + companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64) + if err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Invalid company_id") + } + // a.logger.Info("User ID: " + string(userId.(string))) //panic: interface conversion: interface {} is int64, not string + a.logger.Info("User ID: " + strconv.FormatInt(userId, 10)) + fmt.Printf("User ID: %d\n", userId) + a.logger.Info("Role: " + role) + a.logger.Info("Refresh Token: " + refreshToken) + a.logger.Info("Company ID: " + strconv.FormatInt(companyID, 10)) return c.SendString("Test endpoint") }) a.fiber.Post("/user/resetPassword", handlers.ResetPassword(a.logger, a.userSvc, a.validator)) @@ -26,6 +37,9 @@ func (a *App) initAppRoutes() { a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator)) a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator)) a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc)) + + a.fiber.Get("/user/wallet", a.authMiddleware, handlers.GetCustomerWallet(a.logger, a.walletSvc, a.validator)) + // Swagger a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())