diff --git a/domain/wallet/repository/postgres/get_transaction_list.go b/domain/wallet/repository/postgres/get_transaction_list.go index b8e27d82..5fb31956 100644 --- a/domain/wallet/repository/postgres/get_transaction_list.go +++ b/domain/wallet/repository/postgres/get_transaction_list.go @@ -32,7 +32,7 @@ func (db *DB) GetTransactionListByUserID(ctx context.Context, userID uint64, dbP scanTransaction, countParams, fetchParams, ) if err != nil { - return nil, 0, err + return nil, 0, richerror.New(op).WithErr(err) } return transactionsList, totalItemsCount, nil @@ -101,7 +101,7 @@ func (db *DB) GetTransactionListByUserID(ctx context.Context, userID uint64, dbP } func scanTransaction(scanner postgres.Scanner) (transaction entity.Transaction, err error) { - err = scanner.Scan(&transaction.UserID, &transaction.Currency, &transaction.Amount, &transaction.ActionType, &transaction.Timestamp, &transaction.CreatedAt) + err = scanner.Scan(&transaction.ID, &transaction.UserID, &transaction.Amount, &transaction.Currency, &transaction.ActionType, &transaction.Timestamp, &transaction.CreatedAt) if err != nil { return } diff --git a/domain/wallet/repository/postgres/create_transaction.go b/domain/wallet/repository/postgres/insert_transaction.go similarity index 93% rename from domain/wallet/repository/postgres/create_transaction.go rename to domain/wallet/repository/postgres/insert_transaction.go index 5841d5af..407ab888 100644 --- a/domain/wallet/repository/postgres/create_transaction.go +++ b/domain/wallet/repository/postgres/insert_transaction.go @@ -41,11 +41,11 @@ func (db *DB) InsertTransaction(ctx context.Context, transaction entity.Transact }() - params := []any{transaction.UserID, transaction.Currency, transaction.Amount, transaction.ActionType, transaction.Timestamp} + params := []any{transaction.UserID, transaction.Amount, transaction.Currency, transaction.ActionType, transaction.Timestamp} _, execErr := tx.StmtExecContext(newCtx, stmt, params...) if execErr != nil { - err = richerror.New(op).WithErr(execErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantInsertTransaction) + err = richerror.New(op).WithErr(execErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) return } diff --git a/domain/wallet/repository/postgres/insert_wallet.go b/domain/wallet/repository/postgres/insert_wallet.go new file mode 100644 index 00000000..d0943205 --- /dev/null +++ b/domain/wallet/repository/postgres/insert_wallet.go @@ -0,0 +1,54 @@ +package postgres + +import ( + "context" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" + "git.gocasts.ir/ebhomengo/niki/pkg/database/postgres" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" +) + +func (db *DB) GetWalletByUserID(ctx context.Context, UserID uint64) (entity.Wallet, error) { + + const op = richerror.Op("Wallet.repo.GetWalletByUserID") + + query := `SELECT * FROM wallets WHERE user_id = $1` + + /////////////// use instant query + + wallet, err := postgres.InstantQueryRowContext[entity.Wallet](ctx, postgres.StatementKeyWalletGetUserWallet, query, db.conn, scanWallet, UserID) + if err != nil { + return entity.Wallet{}, richerror.New(op).WithErr(err) + } + + return wallet, nil + + //////////////////////normal + + //stmt, stErr := db.conn.PrepareStatement(ctx, postgres.StatementKeyWalletGetUserWallet, query) + //if stErr != nil { + // return entity.Wallet{}, richerror.New(op).WithErr(stErr) + //} + //walletRow := db.conn.StmtQueryRowContext(ctx, stmt, UserID) + // + //wallet, sErr := scanWallet(walletRow) + // + //if sErr != nil { + // return entity.Wallet{}, richerror.New(op).WithErr(sErr) + //} + // + //return wallet, nil + +} + +func scanWallet(scanner postgres.Scanner) (entity.Wallet, error) { + + var wallet entity.Wallet + + err := scanner.Scan(&wallet.ID, &wallet.UserID, &wallet.Balance, &wallet.Currency, &wallet.Status, &wallet.UpdatedAt) + if err != nil { + return entity.Wallet{}, err + } + + return wallet, nil +} diff --git a/domain/wallet/repository/postgres/wallet.go b/domain/wallet/repository/postgres/wallet.go deleted file mode 100644 index 747ce266..00000000 --- a/domain/wallet/repository/postgres/wallet.go +++ /dev/null @@ -1,40 +0,0 @@ -package postgres - -import ( - "context" - - "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" - "git.gocasts.ir/ebhomengo/niki/pkg/database/postgres" - richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" -) - -func (db *DB) GetWalletByUserID(ctx context.Context, UserID uint64) (entity.Wallet, error) { - const op = richerror.Op("Wallet.repo.GetWalletByUserID") - query := `SELECT * FROM wallets WHERE user_id = $1` - stmt, stErr := db.conn.PrepareStatement(ctx, postgres.StatementKeyWalletGetUserWallet, query) - if stErr != nil { - return entity.Wallet{}, richerror.New(op).WithErr(stErr) - } - walletRow := db.conn.StmtQueryRowContext(ctx, stmt, UserID) - - wallet, sErr := scanWallet(walletRow) - - if sErr != nil { - return entity.Wallet{}, richerror.New(op).WithErr(sErr) - } - - return wallet, nil - -} - -func scanWallet(scanner postgres.Scanner) (entity.Wallet, error) { - - var wallet entity.Wallet - - err := scanner.Scan(&wallet.ID, &wallet.Balance, &wallet.Currency, &wallet.Status, &wallet.UpdatedAt) - if err != nil { - return entity.Wallet{}, err - } - - return wallet, nil -} diff --git a/domain/wallet/service/create_transaction.go b/domain/wallet/service/create_transaction.go index 9a2c5e03..610494b6 100644 --- a/domain/wallet/service/create_transaction.go +++ b/domain/wallet/service/create_transaction.go @@ -22,11 +22,11 @@ func (s Service) CreateTransaction(ctx context.Context, request param.CreateTran Timestamp: request.Timestamp, CreatedAt: time.Now(), } - err := s.repo.InsertTransaction(ctx, transaction) + balance, err := s.repo.InsertTransaction(ctx, transaction) if err != nil { return param.InsertTransactionResponse{}, richerror.New(op).WithErr(err) } - return param.InsertTransactionResponse{}, nil + return param.InsertTransactionResponse{Balance: balance}, nil } diff --git a/domain/wallet/service/service.go b/domain/wallet/service/service.go index a88cbb44..0246f19b 100644 --- a/domain/wallet/service/service.go +++ b/domain/wallet/service/service.go @@ -10,7 +10,7 @@ import ( type Repository interface { GetTransactionListByUserID(ctx context.Context, UserID uint64, DBPagination postgres.DBPagination) ([]entity.Transaction, int64, error) GetWalletByUserID(ctx context.Context, UserID uint64) (entity.Wallet, error) - InsertTransaction(ctx context.Context, transaction entity.Transaction) error + InsertTransaction(ctx context.Context, transaction entity.Transaction) (float64, error) } type Config struct { diff --git a/pkg/database/postgres/db.go b/pkg/database/postgres/db.go index 9d99f803..52721b99 100644 --- a/pkg/database/postgres/db.go +++ b/pkg/database/postgres/db.go @@ -3,10 +3,13 @@ package postgres import ( "context" "database/sql" + "errors" "fmt" "sync" "time" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" _ "github.com/jackc/pgx/v5/stdlib" ) @@ -111,3 +114,96 @@ func (db *DB) StmtExecContext(ctx context.Context, stmt *sql.Stmt, args ...any) return result, nil } + +///////////////////////// generic query + +type ScannerFunc[T any] func(scanner Scanner) (T, error) + +func InstantQueryContext[T any](ctx context.Context, stmtKey statementKey, query string, conn *DB, scanner ScannerFunc[T], args ...any) ([]T, error) { + const op = richerror.Op("postgres.InstantQueryContext") + + readyStmt, err := conn.PrepareStatement(ctx, stmtKey, query) + + if err != nil { + return nil, richerror.New(op).WithMessage(errmsg.ErrorMsgFailedQuery) + } + + rows, qErr := readyStmt.QueryContext(ctx, args...) + + if qErr != nil { + return nil, richerror.New(op).WithMessage(errmsg.ErrorMsgFailedQuery) + } + defer rows.Close() + + var itemsList []T + + for rows.Next() { + + item, sErr := scanner(rows) + if sErr != nil { + return nil, richerror.New(op).WithErr(sErr).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + } + + itemsList = append(itemsList, item) + + } + + if rErr := rows.Err(); rErr != nil { + return nil, richerror.New(op).WithErr(rErr).WithMessage(errmsg.ErrorMsgFailedQuery) + + } + + return itemsList, nil + +} + +func InstantQueryRowContext[T any](ctx context.Context, stmtKey statementKey, query string, conn *DB, scanner ScannerFunc[T], args ...any) (item T, err error) { + const op = richerror.Op("postgres.InstantQueryRowContext") + readyStmt, sErr := conn.PrepareStatement(ctx, stmtKey, query) + + if sErr != nil { + err = richerror.New(op).WithMessage(errmsg.ErrorMsgFailedQuery) + return + } + + row := readyStmt.QueryRowContext(ctx, args...) + + item, scErr := scanner(row) + if scErr != nil { + if errors.Is(scErr, sql.ErrNoRows) { + err = richerror.New(op).WithErr(scErr).WithKind(richerror.KindNotFound).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + return + + } + err = richerror.New(op).WithErr(scErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + + return + + } + + if rErr := row.Err(); rErr != nil { + err = richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + return + } + + return + +} + +func InstantExecContext(ctx context.Context, stmtKey statementKey, query string, conn *DB, args ...any) (sql.Result, error) { + const op = richerror.Op("postgres.InstantExecContext") + + readyStmt, err := conn.PrepareStatement(ctx, stmtKey, query) + + if err != nil { + return nil, richerror.New(op) + } + + result, err := readyStmt.ExecContext(ctx, args...) + if err != nil { + return nil, richerror.New(op) + + } + + return result, nil +} diff --git a/pkg/database/postgres/pagination.go b/pkg/database/postgres/pagination.go index 632ef66f..3a3ddf99 100644 --- a/pkg/database/postgres/pagination.go +++ b/pkg/database/postgres/pagination.go @@ -24,8 +24,6 @@ type DBPagination struct { PageSize int64 } -type ScannerFunc[T any] func(scanner Scanner) (T, error) - func PageNumberPagination[T any](ctx context.Context, countQuery string, fetchQuery string, conn *DB, countQueryStmt statementKey, fetchQueryStmt statementKey, op richerror.Op, scanner ScannerFunc[T], countParams []any, fetchParams []any) ([]T, int64, error) { var totalCount int64 @@ -78,7 +76,7 @@ func PageNumberPagination[T any](ctx context.Context, countQuery string, fetchQu if qErr := queryRows.Err(); qErr != nil { - return nil, 0, richerror.New(op).WithErr(qErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + return nil, 0, richerror.New(op).WithErr(qErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) } return itemsList, totalCount, nil diff --git a/pkg/database/postgres/transaction_handler.go b/pkg/database/postgres/transaction_handler.go index 35fae0fb..cbc52d02 100644 --- a/pkg/database/postgres/transaction_handler.go +++ b/pkg/database/postgres/transaction_handler.go @@ -3,9 +3,12 @@ package postgres import ( "context" "database/sql" + "errors" "fmt" "sync" + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" "github.com/lib/pq" ) @@ -176,3 +179,99 @@ func (tx *TxConn) StmtExecContext(ctx context.Context, stmt *sql.Stmt, args ...a return result, nil } + +///////////////////////// generic query + +func TXInstantQueryContext[T any](ctx context.Context, txConn *sql.Tx, stmtKey statementKey, query string, conn *DB, scanner ScannerFunc[T], args ...any) ([]T, error) { + const op = richerror.Op("postgres.TXInstantQueryContext") + + stmt, err := conn.PrepareStatement(ctx, stmtKey, query) + + if err != nil { + return nil, richerror.New(op).WithMessage(errmsg.ErrorMsgFailedQuery) + } + + txStmt := txConn.StmtContext(ctx, stmt) + + rows, qErr := txStmt.QueryContext(ctx, args...) + + if qErr != nil { + return nil, richerror.New(op).WithMessage(errmsg.ErrorMsgFailedQuery) + } + defer rows.Close() + + var itemsList []T + + for rows.Next() { + + item, sErr := scanner(rows) + if sErr != nil { + return nil, richerror.New(op).WithErr(sErr).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + } + + itemsList = append(itemsList, item) + + } + + if rErr := rows.Err(); rErr != nil { + return nil, richerror.New(op).WithErr(rErr).WithMessage(errmsg.ErrorMsgFailedQuery) + + } + + return itemsList, nil + +} + +func TXInstantQueryRowContext[T any](ctx context.Context, txConn *sql.Tx, stmtKey statementKey, query string, conn *DB, scanner ScannerFunc[T], args ...any) (item T, err error) { + const op = richerror.Op("postgres.TXInstantQueryRowContext") + + stmt, sErr := conn.PrepareStatement(ctx, stmtKey, query) + + if sErr != nil { + err = richerror.New(op).WithMessage(errmsg.ErrorMsgFailedQuery) + return + } + txStmt := txConn.StmtContext(ctx, stmt) + + row := txStmt.QueryRowContext(ctx, args...) + + item, scErr := scanner(row) + if scErr != nil { + if errors.Is(scErr, sql.ErrNoRows) { + err = richerror.New(op).WithErr(scErr).WithKind(richerror.KindNotFound).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + return + + } + err = richerror.New(op).WithErr(scErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + + return + + } + + if rErr := row.Err(); rErr != nil { + err = richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + return + } + + return + +} + +func TXInstantExecContext(ctx context.Context, txConn *sql.Tx, stmtKey statementKey, query string, conn *DB, args ...any) (sql.Result, error) { + const op = richerror.Op("postgres.TXInstantExecContext") + + stmt, err := conn.PrepareStatement(ctx, stmtKey, query) + + if err != nil { + return nil, richerror.New(op) + } + txStmt := txConn.StmtContext(ctx, stmt) + + result, err := txStmt.ExecContext(ctx, args...) + if err != nil { + return nil, richerror.New(op) + + } + + return result, nil +} diff --git a/pkg/err_msg/message.go b/pkg/err_msg/message.go index e12a018c..2049c292 100644 --- a/pkg/err_msg/message.go +++ b/pkg/err_msg/message.go @@ -58,9 +58,7 @@ const ( ErrorMsgInvalidRefreshToken = "invalid refresh token" ErrorMsgInvalidBenefactorStatus = "invalid benefactor status" ErrorMsgInvalidAction = "action invalid" - ErrorMsgCantUpsertBalance = "cant update balance" // wallet - ErrorMsgCantGetBalance = "cant update balance" // wallet - ErrorMsgCantInsertTransaction = "cant insert transaction" // wallet - ErrorMsgFailedQuery = "query failed" // wallet + ErrorMsgCantUpsertBalance = "cant update balance" // wallet + ErrorMsgFailedQuery = "query failed" // wallet )