diff --git a/domain/wallet/repository/postgres/get_transaction_list.go b/domain/wallet/repository/postgres/get_transaction_list.go index d83a54e7..b8e27d82 100644 --- a/domain/wallet/repository/postgres/get_transaction_list.go +++ b/domain/wallet/repository/postgres/get_transaction_list.go @@ -5,66 +5,103 @@ import ( "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" "git.gocasts.ir/ebhomengo/niki/pkg/database/postgres" - errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" ) func (db *DB) GetTransactionListByUserID(ctx context.Context, userID uint64, dbPagination postgres.DBPagination) ([]entity.Transaction, int64, error) { const op = richerror.Op("Wallet.repo.GetTransactionListByUserID") - query := `SELECT * FROM Transactions WHERE user_id = $1 AND transaction_timestamp < $2 ORDER BY transaction_id DESC LIMIT $3` + countQuery := "SELECT COUNT(*) FROM transactions WHERE user_id = $1" + fetchQuery := `SELECT * FROM Transactions WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3` - stmt, StErr := db.conn.PrepareStatement(ctx, postgres.StatementKeyAWalletGetTransactionHistory, query) + countQueryStmt := postgres.StatementKeyAWalletGetTotalCountTransactionHistory + fetchQueryStmt := postgres.StatementKeyAWalletGetTransactionHistory - if StErr != nil { + PageOffset := (dbPagination.PageNumber - 1) * dbPagination.PageSize // offset - return nil, 0, richerror.New(op).WithErr(StErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + PageSize := dbPagination.PageSize //limit - } - - totalRetrieveRecord := (dbPagination.MaxNextPages-1)*dbPagination.PageSize + 1 - - lastTimeStamp := dbPagination.LastTimeStamp - - queryRows, err := stmt.QueryContext(ctx, userID, lastTimeStamp, totalRetrieveRecord) + countParams := []any{userID} // $1 + fetchParams := []any{userID, PageSize, PageOffset} // $1 , $2 , $3 + // + // + /////////////////////////// with generic pagination package + transactionsList, totalItemsCount, err := postgres.PageNumberPagination[entity.Transaction](ctx, countQuery, fetchQuery, + db.conn, countQueryStmt, fetchQueryStmt, op, + scanTransaction, countParams, fetchParams, + ) if err != nil { - return nil, 0, richerror.New(op).WithErr(StErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + return nil, 0, err } - defer queryRows.Close() + return transactionsList, totalItemsCount, nil - var transactions []entity.Transaction + /////////////////////////// normal - var lenCounter int64 - - for queryRows.Next() { - lenCounter++ - - if lenCounter < dbPagination.PageSize+1 { - transaction, err := scanTransaction(queryRows) - - if err != nil { - return nil, 0, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) - } - - transactions = append(transactions, transaction) - - } - - } - - if qErr := queryRows.Err(); qErr != nil { - - return nil, 0, richerror.New(op).WithErr(qErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) - } - - return transactions, lenCounter, nil + //var totalCount int64 + // + //countStmt, CountStErr := db.conn.PrepareStatement(ctx, countQueryStmt, countQuery) + // + //if CountStErr != nil { + // + // return nil, 0, richerror.New(op).WithErr(CountStErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + // + //} + //countErr := countStmt.QueryRowContext(ctx, userID).Scan(&totalCount) + // + //if countErr != nil { + // return nil, 0, richerror.New(op).WithErr(CountStErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + // + //} + // + ////// get records + // + //stmt, StErr := db.conn.PrepareStatement(ctx, fetchQueryStmt, fetchQuery) + // + //if StErr != nil { + // + // return nil, 0, richerror.New(op).WithErr(StErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + // + //} + // + //offset := (dbPagination.PageNumber - 1) * dbPagination.PageSize + // + //limit := dbPagination.PageSize + // + //queryRows, qrErr := stmt.QueryContext(ctx, userID, limit, offset) + // + //if qrErr != nil { + // return nil, 0, richerror.New(op).WithErr(qrErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + //} + // + //defer queryRows.Close() + // + //var transactions []entity.Transaction + // + //for queryRows.Next() { + // + // transaction, err := scanTransaction(queryRows) + // + // if err != nil { + // return nil, 0, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + // } + // + // transactions = append(transactions, transaction) + // + //} + // + //if qErr := queryRows.Err(); qErr != nil { + // + // return nil, 0, richerror.New(op).WithErr(qErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + //} + // + //return transactions, totalCount, nil } func scanTransaction(scanner postgres.Scanner) (transaction entity.Transaction, err error) { - err = scanner.Scan(&transaction.UserID, &transaction.Currency, &transaction.Amount, &transaction.ActionType, &transaction.Timestamp) + err = scanner.Scan(&transaction.UserID, &transaction.Currency, &transaction.Amount, &transaction.ActionType, &transaction.Timestamp, &transaction.CreatedAt) if err != nil { return } diff --git a/domain/wallet/service/service.go b/domain/wallet/service/service.go index 38e97cb2..a88cbb44 100644 --- a/domain/wallet/service/service.go +++ b/domain/wallet/service/service.go @@ -14,8 +14,7 @@ type Repository interface { } type Config struct { - PageSize int64 `koanf:"page_size"` - MaxNextPages int64 `koanf:"max_next_pages"` + PageSize int64 `koanf:"page_size"` } type Service struct { diff --git a/domain/wallet/service/transaction_history.go b/domain/wallet/service/transaction_history.go index 9dc81f90..eaae759f 100644 --- a/domain/wallet/service/transaction_history.go +++ b/domain/wallet/service/transaction_history.go @@ -2,7 +2,6 @@ package service import ( "context" - "time" "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" "git.gocasts.ir/ebhomengo/niki/domain/wallet/param" @@ -12,30 +11,24 @@ import ( func (s Service) GetUserTransactionHistory(ctx context.Context, request param.TransactionRequest) (param.TransactionResponse, error) { const op = richerror.Op("wallet.service.GetUserTransactionHistory") - lastTimeStamp := request.Pagination.LastTimeStamp - if lastTimeStamp.IsZero() { - lastTimeStamp = time.Now() - } dbPagination := postgres.DBPagination{ - LastTimeStamp: lastTimeStamp, - PageNumber: request.Pagination.PageNumber, - MaxNextPages: s.cfg.MaxNextPages, - PageSize: s.cfg.PageSize, + PageNumber: request.Pagination.PageNumber, + PageSize: s.cfg.PageSize, } - transactionList, listLen, err := s.repo.GetTransactionListByUserID(ctx, request.UserID, dbPagination) - - showableNextPagesNum := postgres.ComputeNextPages(listLen, s.cfg.PageSize, s.cfg.MaxNextPages) + transactionList, totalCount, err := s.repo.GetTransactionListByUserID(ctx, request.UserID, dbPagination) if err != nil { return param.TransactionResponse{}, richerror.New(op).WithErr(err) } + totalPages := (totalCount + s.cfg.PageSize - 1) / s.cfg.PageSize + paginationInfo := postgres.ResponsePagination{ - PageNumber: request.Pagination.PageNumber, - PageSize: s.cfg.PageSize, - ShowableNextPagesNum: showableNextPagesNum, + PageNumber: request.Pagination.PageNumber, + PageSize: s.cfg.PageSize, + TotalPages: totalPages, } return param.TransactionResponse{ diff --git a/pkg/database/postgres/pagination.go b/pkg/database/postgres/pagination.go index a79ab45d..632ef66f 100644 --- a/pkg/database/postgres/pagination.go +++ b/pkg/database/postgres/pagination.go @@ -1,35 +1,85 @@ package postgres -import "time" +import ( + "context" + + errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" +) + +// page number pagination type RequestPagination struct { - PageNumber int64 `json:"page_number"` - LastTimeStamp time.Time `json:"last_time_stamp"` + PageNumber int64 `json:"page_number"` } type ResponsePagination struct { - PageNumber int64 `json:"page_number"` - PageSize int64 `json:"page_size"` - ShowableNextPagesNum int64 `json:"showable_next_pages_num"` + PageNumber int64 `json:"page_number"` + PageSize int64 `json:"page_size"` + TotalPages int64 `json:"total_pages"` } type DBPagination struct { - LastTimeStamp time.Time - PageNumber int64 - MaxNextPages int64 - PageSize int64 + PageNumber int64 + PageSize int64 } -func ComputeNextPages(listLen int64, pageSize int64, maxNextPages int64) int64 { +type ScannerFunc[T any] func(scanner Scanner) (T, error) - pages := float64(listLen) / float64(pageSize) +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 + + countStmt, CountStErr := conn.PrepareStatement(ctx, countQueryStmt, countQuery) + + if CountStErr != nil { + + return nil, 0, richerror.New(op).WithErr(CountStErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + + } + countErr := countStmt.QueryRowContext(ctx, countParams...).Scan(&totalCount) + + if countErr != nil { + return nil, 0, richerror.New(op).WithErr(countErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) - for i := maxNextPages - 1; i >= 0; i-- { - if pages > float64(i) { - return i + 1 - } } - return 0 + //// get records + stmt, StErr := conn.PrepareStatement(ctx, fetchQueryStmt, fetchQuery) + + if StErr != nil { + + return nil, 0, richerror.New(op).WithErr(StErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + + } + + queryRows, qrErr := stmt.QueryContext(ctx, fetchParams...) + + if qrErr != nil { + return nil, 0, richerror.New(op).WithErr(qrErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgFailedQuery) + } + + defer queryRows.Close() + + var itemsList []T + + for queryRows.Next() { + + item, err := scanner(queryRows) + + if err != nil { + return nil, 0, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + } + + itemsList = append(itemsList, item) + + } + + if qErr := queryRows.Err(); qErr != nil { + + return nil, 0, richerror.New(op).WithErr(qErr).WithKind(richerror.KindUnexpected).WithMessage(errmsg.ErrorMsgCantScanQueryResult) + } + + return itemsList, totalCount, nil } diff --git a/pkg/database/postgres/prepared_statement.go b/pkg/database/postgres/prepared_statement.go index 5b5382f8..3bfcaa9c 100644 --- a/pkg/database/postgres/prepared_statement.go +++ b/pkg/database/postgres/prepared_statement.go @@ -3,8 +3,9 @@ package postgres type statementKey uint const ( - StatementKeyAWalletGetTransactionHistory statementKey = iota + 1 //wallet - StatementKeyWalletInsertTransaction //wallet - StatementKeyWalletGetUserWallet //wallet - StatementKeyWalletUpsertBalance //wallet + StatementKeyAWalletGetTransactionHistory statementKey = iota + 1 //wallet + StatementKeyAWalletGetTotalCountTransactionHistory //wallet + StatementKeyWalletInsertTransaction //wallet + StatementKeyWalletGetUserWallet //wallet + StatementKeyWalletUpsertBalance //wallet )