Skip to content

Commit

Permalink
Merge branch 'feat/api-transfers-pagination' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
altergui committed Aug 7, 2024
2 parents 820e19e + ca3f738 commit c9cf3e0
Show file tree
Hide file tree
Showing 19 changed files with 533 additions and 279 deletions.
62 changes: 28 additions & 34 deletions api/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"go.vocdoni.io/dvote/log"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/util"
"go.vocdoni.io/dvote/vochain/indexer/indexertypes"
"go.vocdoni.io/dvote/vochain/state"
"go.vocdoni.io/proto/build/go/models"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -433,56 +432,42 @@ func (a *API) accountElectionsCountHandler(_ *apirest.APIdata, ctx *httprouter.H
//
// @Summary List account received and sent token transfers
// @Description Returns the token transfers for an account. A transfer is a token transference from one account to other (excepting the burn address).
// @Deprecated
// @Description (deprecated, in favor of /chain/transfers?accountId=xxx&page=xxx)
// @Tags Accounts
// @Accept json
// @Produce json
// @Param accountId path string true "Specific accountId"
// @Param accountId path string true "Specific accountId that sent or received the tokens"
// @Param page path number true "Page"
// @Success 200 {object} object{transfers=indexertypes.TokenTransfersAccount}
// @Success 200 {object} TransfersList
// @Router /accounts/{accountId}/transfers/page/{page} [get]
func (a *API) tokenTransfersListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
accountID, err := hex.DecodeString(util.TrimHex(ctx.URLParam(ParamAccountId)))
if err != nil || accountID == nil {
return ErrCantParseAccountID.Withf("%q", ctx.URLParam(ParamAccountId))
}
acc, err := a.vocapp.State.GetAccount(common.BytesToAddress(accountID), true)
if acc == nil {
return ErrAccountNotFound
}
if err != nil {
return err
}

page, err := parsePage(ctx.URLParam(ParamPage))
params, err := parseTransfersParams(
ctx.URLParam(ParamPage),
"",
ctx.URLParam(ParamAccountId),
"",
"",
)
if err != nil {
return err
}

transfers, err := a.indexer.GetTokenTransfersByAccount(accountID, int32(page*DefaultItemsPerPage), DefaultItemsPerPage)
if err != nil {
return ErrCantFetchTokenTransfers.WithErr(err)
}
data, err := json.Marshal(
struct {
Transfers indexertypes.TokenTransfersAccount `json:"transfers"`
}{Transfers: transfers},
)
if err != nil {
return ErrMarshalingServerJSONFailed.WithErr(err)
}
return ctx.Send(data, apirest.HTTPstatusOK)
return a.sendTransfersList(ctx, params)
}

// tokenFeesHandler
//
// @Summary List account token fees
// @Description Returns the token fees for an account. A spending is an amount of tokens burnt from one account for executing transactions.
// @Deprecated
// @Description (deprecated, in favor of /chain/transfers?accountId=xxx&page=xxx)
// @Tags Accounts
// @Accept json
// @Produce json
// @Param accountId path string true "Specific accountId"
// @Param page path number true "Page"
// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta}
// @Success 200 {object} FeesList
// @Router /accounts/{accountId}/fees/page/{page} [get]
func (a *API) tokenFeesHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
params, err := parseFeesParams(
Expand Down Expand Up @@ -549,6 +534,7 @@ func (a *API) accountListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPC
params, err := parseAccountParams(
ctx.URLParam(ParamPage),
"",
"",
)
if err != nil {
return err
Expand All @@ -563,14 +549,16 @@ func (a *API) accountListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPC
// @Tags Accounts
// @Accept json
// @Produce json
// @Param page query number false "Page"
// @Param limit query number false "Items per page"
// @Success 200 {object} AccountsList
// @Param page query number false "Page"
// @Param limit query number false "Items per page"
// @Param accountId query string false "Filter by partial accountId"
// @Success 200 {object} AccountsList
// @Router /accounts [get]
func (a *API) accountListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
params, err := parseAccountParams(
ctx.QueryParam(ParamPage),
ctx.QueryParam(ParamLimit),
ctx.QueryParam(ParamAccountId),
)
if err != nil {
return err
Expand All @@ -583,9 +571,14 @@ func (a *API) accountListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext
//
// Errors returned are always of type APIerror.
func (a *API) sendAccountList(ctx *httprouter.HTTPContext, params *AccountParams) error {
if params.AccountID != "" && !a.indexer.AccountExists(params.AccountID) {
return ErrAccountNotFound
}

accounts, total, err := a.indexer.AccountList(
params.Limit,
params.Page*params.Limit,
params.AccountID,
)
if err != nil {
return ErrIndexerQueryFailed.WithErr(err)
Expand All @@ -604,13 +597,14 @@ func (a *API) sendAccountList(ctx *httprouter.HTTPContext, params *AccountParams
}

// parseAccountParams returns an AccountParams filled with the passed params
func parseAccountParams(paramPage, paramLimit string) (*AccountParams, error) {
func parseAccountParams(paramPage, paramLimit, paramAccountID string) (*AccountParams, error) {
pagination, err := parsePaginationParams(paramPage, paramLimit)
if err != nil {
return nil, err
}

return &AccountParams{
PaginationParams: pagination,
AccountID: util.TrimHex(paramAccountID),
}, nil
}
2 changes: 2 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const (
ParamHeight = "height"
ParamReference = "reference"
ParamType = "type"
ParamAccountIdFrom = "accountIdFrom"
ParamAccountIdTo = "accountIdTo"
)

var (
Expand Down
15 changes: 15 additions & 0 deletions api/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type OrganizationParams struct {
// AccountParams allows the client to filter accounts
type AccountParams struct {
PaginationParams
AccountID string `json:"accountId,omitempty"`
}

// TransactionParams allows the client to filter transactions
Expand All @@ -57,6 +58,14 @@ type FeesParams struct {
AccountID string `json:"accountId,omitempty"`
}

// TransfersParams allows the client to filter transfers
type TransfersParams struct {
PaginationParams
AccountID string `json:"accountId,omitempty"`
AccountIDFrom string `json:"accountIdFrom,omitempty"`
AccountIDTo string `json:"accountIdTo,omitempty"`
}

// VoteParams allows the client to filter votes
type VoteParams struct {
PaginationParams
Expand Down Expand Up @@ -264,6 +273,12 @@ type FeesList struct {
Pagination *Pagination `json:"pagination"`
}

// TransfersList is used to return a paginated list to the client
type TransfersList struct {
Transfers []*indexertypes.TokenTransferMeta `json:"transfers"`
Pagination *Pagination `json:"pagination"`
}

type GenericTransactionWithInfo struct {
TxContent json.RawMessage `json:"tx"`
TxInfo indexertypes.Transaction `json:"txInfo"`
Expand Down
94 changes: 94 additions & 0 deletions api/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ func (a *API) enableChainHandlers() error {
); err != nil {
return err
}
if err := a.Endpoint.RegisterMethod(
"/chain/transfers",
"GET",
apirest.MethodAccessTypePublic,
a.chainTransfersListHandler,
); err != nil {
return err
}
if err := a.Endpoint.RegisterMethod(
"/chain/export/indexer",
"GET",
Expand Down Expand Up @@ -324,6 +332,10 @@ func (a *API) organizationListByFilterAndPageHandler(msg *apirest.APIdata, ctx *
//
// Errors returned are always of type APIerror.
func (a *API) sendOrganizationList(ctx *httprouter.HTTPContext, params *OrganizationParams) error {
if params.OrganizationID != "" && !a.indexer.EntityExists(params.OrganizationID) {
return ErrOrgNotFound
}

orgs, total, err := a.indexer.EntityList(
params.Limit,
params.Page*params.Limit,
Expand Down Expand Up @@ -1053,6 +1065,10 @@ func (a *API) chainFeesListByTypeAndPageHandler(_ *apirest.APIdata, ctx *httprou
//
// Errors returned are always of type APIerror.
func (a *API) sendFeesList(ctx *httprouter.HTTPContext, params *FeesParams) error {
if params.AccountID != "" && !a.indexer.AccountExists(params.AccountID) {
return ErrAccountNotFound
}

fees, total, err := a.indexer.TokenFeesList(
params.Limit,
params.Page*params.Limit,
Expand All @@ -1076,6 +1092,69 @@ func (a *API) sendFeesList(ctx *httprouter.HTTPContext, params *FeesParams) erro
return marshalAndSend(ctx, list)
}

// chainTransfersListHandler
//
// @Summary List all token transfers
// @Description Returns the token transfers list ordered by date.
// @Tags Chain
// @Accept json
// @Produce json
// @Param page query number false "Page"
// @Param limit query number false "Items per page"
// @Param accountId query string false "Specific accountId that sent or received the tokens"
// @Param accountIdFrom query string false "Specific accountId that sent the tokens"
// @Param accountIdTo query string false "Specific accountId that received the tokens"
// @Success 200 {object} TransfersList
// @Router /chain/transfers [get]
func (a *API) chainTransfersListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
params, err := parseTransfersParams(
ctx.QueryParam(ParamPage),
ctx.QueryParam(ParamLimit),
ctx.QueryParam(ParamAccountId),
ctx.QueryParam(ParamAccountIdFrom),
ctx.QueryParam(ParamAccountIdTo),
)
if err != nil {
return err
}

return a.sendTransfersList(ctx, params)
}

// sendTransfersList produces a filtered, paginated TokenTransfersList,
// and sends it marshalled over ctx.Send
//
// Errors returned are always of type APIerror.
func (a *API) sendTransfersList(ctx *httprouter.HTTPContext, params *TransfersParams) error {
for _, param := range []string{params.AccountID, params.AccountIDFrom, params.AccountIDTo} {
if param != "" && !a.indexer.AccountExists(param) {
return ErrAccountNotFound.With(param)
}
}

transfers, total, err := a.indexer.TokenTransfersList(
params.Limit,
params.Page*params.Limit,
params.AccountID,
params.AccountIDFrom,
params.AccountIDTo,
)
if err != nil {
return ErrIndexerQueryFailed.WithErr(err)
}

pagination, err := calculatePagination(params.Page, params.Limit, total)
if err != nil {
return err
}

list := &TransfersList{
Transfers: transfers,
Pagination: pagination,
}
return marshalAndSend(ctx, list)
}

// chainIndexerExportHandler
//
// @Summary Exports the indexer database
Expand Down Expand Up @@ -1122,6 +1201,21 @@ func parseFeesParams(paramPage, paramLimit, paramReference, paramType, paramAcco
}, nil
}

// parseTransfersParams returns an TransfersParams filled with the passed params
func parseTransfersParams(paramPage, paramLimit, paramAccountId, paramAccountIdFrom, paramAccountIdTo string) (*TransfersParams, error) {
pagination, err := parsePaginationParams(paramPage, paramLimit)
if err != nil {
return nil, err
}

return &TransfersParams{
PaginationParams: pagination,
AccountID: util.TrimHex(paramAccountId),
AccountIDFrom: util.TrimHex(paramAccountIdFrom),
AccountIDTo: util.TrimHex(paramAccountIdTo),
}, nil
}

// parseTransactionParams returns an TransactionParams filled with the passed params
func parseTransactionParams(paramPage, paramLimit, paramHeight, paramType string) (*TransactionParams, error) {
pagination, err := parsePaginationParams(paramPage, paramLimit)
Expand Down
8 changes: 8 additions & 0 deletions api/elections.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@ func (a *API) electionListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContex
//
// Errors returned are always of type APIerror.
func (a *API) sendElectionList(ctx *httprouter.HTTPContext, params *ElectionParams) error {
if params.ElectionID != "" && !a.indexer.ProcessExists(params.ElectionID) {
return ErrElectionNotFound
}

if params.OrganizationID != "" && !a.indexer.EntityExists(params.OrganizationID) {
return ErrOrgNotFound
}

status, err := parseStatus(params.Status)
if err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions api/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ func (a *API) votesListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
//
// Errors returned are always of type APIerror.
func (a *API) sendVotesList(ctx *httprouter.HTTPContext, params *VoteParams) error {
if params.ElectionID != "" && !a.indexer.ProcessExists(params.ElectionID) {
return ErrElectionNotFound
}

votes, total, err := a.indexer.VoteList(
params.Limit,
params.Page*params.Limit,
Expand Down
16 changes: 7 additions & 9 deletions apiclient/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,21 +290,19 @@ func (c *HTTPclient) AccountSetMetadata(metadata *api.AccountMetadata) (types.He
}

// ListTokenTransfers returns the list of sent and received token transfers associated with an account
func (c *HTTPclient) ListTokenTransfers(account common.Address, page int) (indexertypes.TokenTransfersAccount, error) {
func (c *HTTPclient) ListTokenTransfers(account common.Address, page int) (*api.TransfersList, error) {
resp, code, err := c.Request(HTTPGET, nil, "accounts", account.Hex(), "transfers", "page", strconv.Itoa(page))
if err != nil {
return indexertypes.TokenTransfersAccount{}, err
return nil, err
}
if code != apirest.HTTPstatusOK {
return indexertypes.TokenTransfersAccount{}, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
return nil, fmt.Errorf("%s: %d (%s)", errCodeNot200, code, resp)
}
tokenTxs := new(struct {
Transfers indexertypes.TokenTransfersAccount `json:"transfers"`
})
if err := json.Unmarshal(resp, &tokenTxs); err != nil {
return indexertypes.TokenTransfersAccount{}, err
tokenTxs := &api.TransfersList{}
if err := json.Unmarshal(resp, tokenTxs); err != nil {
return nil, err
}
return tokenTxs.Transfers, nil
return tokenTxs, nil
}

// SetSIK function allows to update the Secret Identity Key for the current
Expand Down
6 changes: 2 additions & 4 deletions cmd/end2endtest/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,12 @@ func checkTokenTransfersCount(api *apiclient.HTTPclient, address common.Address)
if err != nil {
return err
}
countTokenTxs := uint64(len(tokenTxs.Received) + len(tokenTxs.Sent))

count, err := api.CountTokenTransfers(address)
if err != nil {
return err
}
if count != countTokenTxs {
return fmt.Errorf("expected %s to match transfers count %d and %d", address, count, countTokenTxs)
if count != tokenTxs.Pagination.TotalItems {
return fmt.Errorf("expected %s to match transfers count %d and %d", address, count, tokenTxs.Pagination.TotalItems)
}

log.Infow("current transfers count", "account", address.String(), "count", count)
Expand Down
Loading

0 comments on commit c9cf3e0

Please sign in to comment.