Skip to content

Commit

Permalink
feat(SPV-996): search endpoint transactions and contacts (#683)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubmkowalski authored Sep 10, 2024
1 parent bf555b1 commit 32ecc47
Show file tree
Hide file tree
Showing 14 changed files with 866 additions and 139 deletions.
58 changes: 44 additions & 14 deletions actions/contacts/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/bitcoin-sv/spv-wallet/actions/common"
"github.com/bitcoin-sv/spv-wallet/engine"
"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"
"github.com/bitcoin-sv/spv-wallet/models/filter"
Expand Down Expand Up @@ -77,39 +78,68 @@ func search(c *gin.Context, userContext *reqctx.UserContext) {
c.JSON(http.StatusOK, response)
}

// TODO: this method will be changed based on search poc
// getContacts will fetch a list of contacts
// @Summary Get contacts
// @Description Get contacts
// @Tags Contacts
// @Produce json
// @Param SearchContacts body filter.SearchContacts false "Supports targeted resource searches with filters and metadata, plus options for pagination and sorting to streamline data exploration and analysis"
// @Param SwaggerCommonParams query swagger.CommonFilteringQueryParams false "Supports options for pagination and sorting to streamline data exploration and analysis"
// @Param ContactParams query filter.ContactFilter false "Supports targeted resource searches with filters"
// @Success 200 {object} response.PageModel[response.Contact] "Page of contacts"
// @Failure 400 "Bad request - Error while parsing SearchContacts from request body"
// @Failure 500 "Internal server error - Error while searching for contacts"
// @Router /api/v1/contacts [get]
// @Security x-auth-xpub
func getContacts(c *gin.Context, userContext *reqctx.UserContext) {
contacts, count := searchContacts(c, userContext.GetXPubID(), "")
if contacts == nil {
logger := reqctx.Logger(c)
engine := reqctx.Engine(c)
reqXPubID := userContext.GetXPubID()

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

contracts := mappings.MapToContactContracts(contacts)
conditions, err := searchParams.Conditions.ToDbConditions()
if err != nil {
spverrors.ErrorResponse(c, spverrors.ErrInvalidConditions, logger)
return
}
metadata := mappings.MapToMetadata(searchParams.Metadata)
pageOptions := mappings.MapToDbQueryParams(&searchParams.Page)

contacts, err := engine.GetContactsByXpubID(
c.Request.Context(),
reqXPubID,
metadata,
conditions,
pageOptions,
)
if err != nil {
spverrors.ErrorResponse(c, err, logger)
return
}

totalPages := 0
if int(count) != 0 {
totalPages = len(contracts) / int(count)
contracts := make([]*response.Contact, 0)
for _, contact := range contacts {
contracts = append(contracts, mappings.MapToContactContract(contact))
}

count, err := engine.GetContactsByXPubIDCount(
c.Request.Context(),
reqXPubID,
metadata,
conditions,
)

if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
response := response.PageModel[response.Contact]{
Content: contracts,
Page: response.PageDescription{
Size: len(contracts),
Number: 0,
TotalElements: int(count),
TotalPages: totalPages,
},
Page: common.GetPageDescriptionFromSearchParams(pageOptions, count),
}

c.JSON(http.StatusOK, response)
Expand Down
6 changes: 3 additions & 3 deletions actions/transactions/count.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import (

// count will fetch a count of transactions filtered on conditions and metadata
// Count of transactions godoc
// @Summary Count of transactions
// @Description The functionality of this method will be offered in the future by /api/v1/transactions [get].
// @Summary Count of transactions - Use (GET) /api/v1/transactions instead.
// @Description This endpoint has been deprecated. Use (GET) /api/v1/transactions instead
// @Tags Transactions
// @Produce json
// @Param CountTransactions body filter.CountTransactions false "Enables filtering of elements to be counted"
// @Success 200 {number} int64 "Count of access keys"
// @Failure 400 "Bad request - Error while parsing CountTransactions from request body"
// @Failure 500 "Internal Server Error - Error while fetching count of transactions"
// @Router /v1/transaction/count [post]
// @DeprecatedRouter /v1/transaction/count [post]
// @Security x-auth-xpub
func count(c *gin.Context, userContext *reqctx.UserContext) {
logger := reqctx.Logger(c)
Expand Down
56 changes: 35 additions & 21 deletions actions/transactions/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package transactions
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"
"github.com/bitcoin-sv/spv-wallet/models/filter"
Expand All @@ -14,15 +16,15 @@ import (

// search will fetch a list of transactions filtered on conditions and metadata
// Search transaction godoc
// @Summary Search transaction
// @Description Search transaction
// @Summary Search transaction - Use (GET) /api/v1/transactions instead.
// @Description This endpoint has been deprecated. Use (GET) /api/v1/transactions instead
// @Tags Transactions
// @Produce json
// @Param SearchTransactions body filter.SearchTransactions false "Supports targeted resource searches with filters and metadata, plus options for pagination and sorting to streamline data exploration and analysis"
// @Success 200 {object} []models.Transaction "List of transactions"
// @Failure 400 "Bad request - Error while parsing SearchTransactions from request body"
// @Failure 500 "Internal server error - Error while searching for transactions"
// @Router /v1/transaction/search [post]
// @DeprecatedRouter /v1/transaction/search [post]
// @Security x-auth-xpub
func search(c *gin.Context, userContext *reqctx.UserContext) {
logger := reqctx.Logger(c)
Expand Down Expand Up @@ -54,36 +56,42 @@ func search(c *gin.Context, userContext *reqctx.UserContext) {
c.JSON(http.StatusOK, contracts)
}

// TODO: this method is not finished and will be changed based on search poc
// transactions will fetch a list of transactions filtered on conditions and metadata
// Get transactions godoc
// @Summary Experimental - Get transactions
// @Description Experimental (not ready for production use yet) - Get transactions
// @Summary Get transactions
// @Description Get transactions
// @Tags Transactions
// @Produce json
// @Param SearchTransactions body filter.SearchTransactions false "Supports targeted resource searches with filters and metadata, plus options for pagination and sorting to streamline data exploration and analysis"
// @Param SwaggerCommonParams query swagger.CommonFilteringQueryParams false "Supports options for pagination and sorting to streamline data exploration and analysis"
// @Param TransactionParams query filter.TransactionFilter false "Supports targeted resource searches with filters"
// @Success 200 {object} response.PageModel[response.Transaction] "Page of transactions"
// @Failure 400 "Bad request - Error while parsing SearchTransactions from request body"
// @Failure 500 "Internal server error - Error while searching for transactions"
// @Router /api/v1/transactions [get]
// @Security x-auth-xpub
func transactions(c *gin.Context, userContext *reqctx.UserContext) {
var reqParams filter.SearchTransactions
if err := c.Bind(&reqParams); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
reqXPubID := userContext.GetXPubID()

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

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

// Record a new transaction (get the hex from parameters)
transactions, err := reqctx.Engine(c).GetTransactionsByXpubID(
c.Request.Context(),
userContext.GetXPubID(),
mappings.MapToMetadata(reqParams.Metadata),
reqParams.Conditions.ToDbConditions(),
mappings.MapToQueryParams(reqParams.QueryParams),
reqXPubID,
metadata,
conditions,
pageOptions,
)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
spverrors.ErrorResponse(c, err, reqctx.Logger(c))
return
}

Expand All @@ -92,14 +100,20 @@ func transactions(c *gin.Context, userContext *reqctx.UserContext) {
contracts = append(contracts, mappings.MapToTransactionContract(transaction))
}

count, err := reqctx.Engine(c).GetTransactionsByXpubIDCount(
c.Request.Context(),
reqXPubID,
metadata,
conditions,
)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}

result := response.PageModel[response.Transaction]{
Content: contracts,
Page: response.PageDescription{
Size: len(contracts),
Number: 0,
TotalElements: len(contracts),
TotalPages: 1,
},
Page: common.GetPageDescriptionFromSearchParams(pageOptions, count),
}
c.JSON(http.StatusOK, result)
}
Loading

0 comments on commit 32ecc47

Please sign in to comment.