Skip to content

Commit

Permalink
Feat/spv 1119/add a search option by xpubid for admin only (#757)
Browse files Browse the repository at this point in the history
Co-authored-by: Augustyn Chmiel <augustyn.chmiel@4chain.studio>
  • Loading branch information
ac4ch and ac4ch authored Oct 31, 2024
1 parent b2d1207 commit 2a9667c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 25 deletions.
72 changes: 72 additions & 0 deletions actions/admin/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package admin

import (
"fmt"
"net/http"

"github.com/bitcoin-sv/spv-wallet/actions/common"
"github.com/bitcoin-sv/spv-wallet/engine"
"github.com/bitcoin-sv/spv-wallet/engine/datastore"
"github.com/bitcoin-sv/spv-wallet/mappings"
"github.com/bitcoin-sv/spv-wallet/models/filter"
"github.com/bitcoin-sv/spv-wallet/models/response"
"github.com/bitcoin-sv/spv-wallet/server/reqctx"
"github.com/gin-gonic/gin"
)

// Helper function to prepare transaction query parameters
func prepareQueryParams(c *gin.Context, searchParams *filter.SearchParams[filter.AdminTransactionFilter]) *transactionQueryParams {
return &transactionQueryParams{
Context: c.Request.Context(),
XPubID: searchParams.Conditions.XPubID,
Metadata: mappings.MapToMetadata(searchParams.Metadata),
Conditions: searchParams.Conditions.ToDbConditions(),
PageOptions: mappings.MapToDbQueryParams(&searchParams.Page),
}
}

// Helper function to fetch transactions based on XPubID presence
func fetchTransactions(c *gin.Context, params *transactionQueryParams) ([]*engine.Transaction, error) {
if params.XPubID != nil {
transactions, err := reqctx.Engine(c).GetTransactionsByXpubID(params.Context, *params.XPubID, params.Metadata, params.Conditions, params.PageOptions)
if err != nil {
return nil, fmt.Errorf("fetch transactions by XPubID failed: %w", err)
}
return transactions, nil
}
transactions, err := reqctx.Engine(c).GetTransactions(params.Context, params.Metadata, params.Conditions, params.PageOptions)
if err != nil {
return nil, fmt.Errorf("fetch transactions failed: %w", err)
}
return transactions, nil
}

// Helper function to count transactions based on XPubID presence
func countTransactions(c *gin.Context, params *transactionQueryParams) (int64, error) {
if params.XPubID != nil {
count, err := reqctx.Engine(c).GetTransactionsByXpubIDCount(params.Context, *params.XPubID, params.Metadata, params.Conditions)
if err != nil {
return 0, fmt.Errorf("count transactions by XPubID failed: %w", err)
}
return count, nil
}

count, err := reqctx.Engine(c).GetTransactionsCount(params.Context, params.Metadata, params.Conditions)
if err != nil {
return 0, fmt.Errorf("count transactions failed: %w", err)
}
return count, nil
}

// Helper function to map transactions and send the response
// sendPaginatedResponse sends a paginated response with any content type.
func sendPaginatedResponse[T any, U any](c *gin.Context, content []*T, pageOptions *datastore.QueryParams, count int64, mapToContractFunc func(*T) *U) {
contracts := common.MapToTypeContracts(content, mapToContractFunc)

result := response.PageModel[U]{
Content: contracts,
Page: common.GetPageDescriptionFromSearchParams(pageOptions, count),
}

c.JSON(http.StatusOK, result)
}
12 changes: 12 additions & 0 deletions actions/admin/models.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package admin

import (
"context"

"github.com/bitcoin-sv/spv-wallet/engine"
"github.com/bitcoin-sv/spv-wallet/engine/datastore"
)

// CreatePaymail is the model for creating a paymail
Expand Down Expand Up @@ -45,3 +48,12 @@ type UpdateContact struct {
// New name for the contact
FullName string `json:"fullName" example:"John Doe"`
}

// Helper struct for transaction query params
type transactionQueryParams struct {
Context context.Context
XPubID *string
Metadata *engine.Metadata
Conditions map[string]any
PageOptions *datastore.QueryParams
}
31 changes: 6 additions & 25 deletions actions/admin/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ package admin
import (
"net/http"

"github.com/bitcoin-sv/spv-wallet/actions/common"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/internal/query"
"github.com/bitcoin-sv/spv-wallet/mappings"
"github.com/bitcoin-sv/spv-wallet/models/filter"
"github.com/bitcoin-sv/spv-wallet/models/response"
"github.com/bitcoin-sv/spv-wallet/server/reqctx"
"github.com/gin-gonic/gin"
)
Expand Down Expand Up @@ -59,42 +57,25 @@ func adminGetTxByID(c *gin.Context, _ *reqctx.AdminContext) {
func adminSearchTxs(c *gin.Context, _ *reqctx.AdminContext) {
logger := reqctx.Logger(c)

searchParams, err := query.ParseSearchParams[filter.TransactionFilter](c)
searchParams, err := query.ParseSearchParams[filter.AdminTransactionFilter](c)
if err != nil {
spverrors.ErrorResponse(c, spverrors.ErrCannotParseQueryParams.WithTrace(err), logger)
return
}

conditions := searchParams.Conditions.ToDbConditions()
metadata := mappings.MapToMetadata(searchParams.Metadata)
pageOptions := mappings.MapToDbQueryParams(&searchParams.Page)
queryParams := prepareQueryParams(c, searchParams)

transactions, err := reqctx.Engine(c).GetTransactions(
c.Request.Context(),
metadata,
conditions,
pageOptions,
)
transactions, err := fetchTransactions(c, queryParams)
if err != nil {
spverrors.ErrorResponse(c, err, logger)
spverrors.ErrorResponse(c, spverrors.ErrFetchTransactions.Wrap(err), logger)
return
}

count, err := reqctx.Engine(c).GetTransactionsCount(
c.Request.Context(),
metadata,
conditions,
)
count, err := countTransactions(c, queryParams)
if err != nil {
spverrors.ErrorResponse(c, spverrors.ErrCouldNotCountTransactions.WithTrace(err), logger)
return
}

contracts := common.MapToTypeContracts(transactions, mappings.MapToTransactionContractForAdmin)
result := response.PageModel[response.Transaction]{
Content: contracts,
Page: common.GetPageDescriptionFromSearchParams(pageOptions, count),
}

c.JSON(http.StatusOK, result)
sendPaginatedResponse(c, transactions, queryParams.PageOptions, count, mappings.MapToTransactionContractForAdmin)
}
6 changes: 6 additions & 0 deletions engine/spverrors/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ var ErrInvalidTransactionID = models.SPVError{Message: "invalid transaction id",
// ErrCouldNotCountTransactions is when a transaction count fails
var ErrCouldNotCountTransactions = models.SPVError{Message: "failed transactions count", StatusCode: 500, Code: "error-transactions-count-failed"}

// ErrFetchTransactions is when a transaction fetch fails
var ErrFetchTransactions = models.SPVError{Message: "failed to fetch transactions", StatusCode: 500, Code: "error-fetch-transactions"}

// ErrInvalidRequirements is when an invalid requirement was given
var ErrInvalidRequirements = models.SPVError{Message: "requirements are invalid or missing", StatusCode: 400, Code: "error-transaction-requirements-invalid"}

Expand Down Expand Up @@ -440,3 +443,6 @@ var ErrInvalidInt64 = models.SPVError{Message: "invalid int64 value", StatusCode

// ErrInvalidUint64 is when uint64 value is invalid
var ErrInvalidUint64 = models.SPVError{Message: "invalid uint64 value", StatusCode: 500, Code: "error-invalid-uint64"}

// ErrMissingXPubID is when xpub_id is missing
var ErrMissingXPubID = models.SPVError{Message: "missing xpub_id", StatusCode: 400, Code: "error-missing-xpub-id"}
15 changes: 15 additions & 0 deletions models/filter/transaction_admin_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package filter

// AdminTransactionFilter extends TransactionFilter for admin-specific use, including xpubid filtering
type AdminTransactionFilter struct {
//lint:ignore SA5008 We want to reuse json tags also to mapstructure.
TransactionFilter `json:",inline,squash"`
XPubID *string `json:"xpubid,omitempty" example:"623bc25ce1c0fc510dea72b5ee27b2e70384c099f1f3dce9e73dd987198c3486"`
}

// ToDbConditions converts filter fields to the datastore conditions for admin-specific queries
func (f *AdminTransactionFilter) ToDbConditions() map[string]interface{} {
conditions := f.TransactionFilter.ToDbConditions()

return conditions
}

0 comments on commit 2a9667c

Please sign in to comment.