Skip to content

Commit

Permalink
api: general refactor (pagination, query params)
Browse files Browse the repository at this point in the history
* indexer: rename GetListAccounts -> AccountsList
* indexer: rename GetEnvelopes -> VoteList
* indexer: replace GetTokenFees* methods with a single TokenFeesList
* indexer: AccountsList, ProcessList, EntityList, VoteList, TokenFeesList now return a TotalCount
* indexer: EntityList inverted order of args (from, max) to be consistent with others

* test: add TestAPIAccountsList and TestAPIElectionsList

* api: unify hardcoded structs into a new types:
  * AccountsList
  * ElectionsList
  * OrganizationsList
  * FeesList
  * CountResult

* api: add `pagination` field to the reply of these endpoints:
  * GET /elections
  * GET /accounts
  * GET /chain/organizations
  * GET /chain/fees
  * GET /votes

* api: add new QueryParams to some endpoints:
  * GET /elections
    * page
    * limit
    * status
    * organizationId
    * electionId
    * withResults

  * GET /accounts
    * page
    * limit

  * GET /chain/organizations
    * page
    * limit
    * organizationId

  * GET /votes
    * page
    * limit
    * electionId

  * GET /chain/fees
    * page
    * limit
    * reference
    * type
    * accountId

* api: refactor filtered endpoints to unify pagination logic (and add `pagination` field):
  * GET /accounts/{organizationID}/elections/status/{status}/page/{page}
  * GET /accounts/{organizationID}/elections/page/{page}
  * GET /elections/page/{page}
  * POST /elections/filter/page/{page}
  * GET /chain/organizations/page/{page}
  * POST /chain/organizations/filter/page/{page}
  * GET /accounts/page/{page}
  * GET /elections/{electionId}/votes/page/{page}
also, marked all of these endpoints as deprecated on swagger docs

* api: return ErrPageNotFound on paginated endpoints, when page is negative or higher than last_page

* api: deduplicate several code snippets, with marshalAndSend and parse* helpers
* api: rename const MaxPageSize -> MaxItemsPerPage
* api: now client can pass a `limit` param (fallbacks to DefaultItemsPerPage and can't surpass hardcoded MaxItemsPerPage)

* api: fix strings in errors returned to client, replacing "ID" -> "Id"
* api: fix swagger docs, replace many occurences of "ID" -> "Id"
* api: fix swagger docs, lots of small inaccuracies
  • Loading branch information
altergui committed Jul 24, 2024
1 parent 6935d34 commit f50c206
Show file tree
Hide file tree
Showing 30 changed files with 1,691 additions and 1,167 deletions.
337 changes: 170 additions & 167 deletions api/accounts.go

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ import (

// @securityDefinitions.basic BasicAuth

// MaxPageSize defines the maximum number of results returned by the paginated endpoints
const MaxPageSize = 10
const (
// DefaultItemsPerPage defines how many items per page are returned by the paginated endpoints,
// when the client doesn't specify a `limit` param
DefaultItemsPerPage = 10
// MaxItemsPerPage defines a ceiling for the `limit` param passed by the client
MaxItemsPerPage = 100
)

// These consts define the keywords for query (?param=), url (/url/param/) and POST params.
// Note: In JS/TS acronyms like "ID" are camelCased as in "Id".
Expand All @@ -61,8 +66,12 @@ const (
ParamOrganizationId = "organizationId"
ParamVoteId = "voteId"
ParamPage = "page"
ParamLimit = "limit"
ParamStatus = "status"
ParamWithResults = "withResults"
ParamHeight = "height"
ParamReference = "reference"
ParamType = "type"
)

var (
Expand Down
117 changes: 103 additions & 14 deletions api/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,81 @@ import (
"google.golang.org/protobuf/encoding/protojson"
)

type Organization struct {
OrganizationID types.HexBytes `json:"organizationID,omitempty" `
Elections []*ElectionSummary `json:"elections,omitempty"`
Organizations []*OrganizationList `json:"organizations,omitempty"`
Count *uint64 `json:"count,omitempty" example:"1"`
// ### Params accepted ###

// PaginationParams allows the client to request a specific page, and how many items per page
type PaginationParams struct {
Page int `json:"page,omitempty"`
Limit int `json:"limit,omitempty"`
}

// ElectionParams allows the client to filter elections
type ElectionParams struct {
PaginationParams
OrganizationID string `json:"organizationId,omitempty"`
ElectionID string `json:"electionId,omitempty"`
WithResults bool `json:"withResults,omitempty"`
Status string `json:"status,omitempty"`
}

// OrganizationParams allows the client to filter organizations
type OrganizationParams struct {
PaginationParams
OrganizationID string `json:"organizationId,omitempty"`
}

// AccountParams allows the client to filter accounts
type AccountParams struct {
PaginationParams
}

// TransactionParams allows the client to filter transactions
type TransactionParams struct {
PaginationParams
Height uint64 `json:"height,omitempty"`
}

// FeesParams allows the client to filter fees
type FeesParams struct {
PaginationParams
Reference string `json:"reference,omitempty"`
Type string `json:"type,omitempty"`
AccountID string `json:"accountId,omitempty"`
}

// VoteParams allows the client to filter votes
type VoteParams struct {
PaginationParams
ElectionID string `json:"electionId,omitempty"`
}

// ### Objects returned ###

// CountResult wraps a count inside an object
type CountResult struct {
Count uint64 `json:"count" example:"10"`
}

type OrganizationList struct {
// Pagination contains all the values needed for the UI to easily organize the returned data
type Pagination struct {
TotalItems uint64 `json:"totalItems"`
PreviousPage *uint64 `json:"previousPage"`
CurrentPage uint64 `json:"currentPage"`
NextPage *uint64 `json:"nextPage"`
LastPage uint64 `json:"lastPage"`
}

type OrganizationSummary struct {
OrganizationID types.HexBytes `json:"organizationID" example:"0x370372b92514d81a0e3efb8eba9d036ae0877653"`
ElectionCount uint64 `json:"electionCount" example:"1"`
}

// OrganizationsList is used to return a paginated list to the client
type OrganizationsList struct {
Organizations []OrganizationSummary `json:"organizations"`
Pagination *Pagination `json:"pagination"`
}

type ElectionSummary struct {
ElectionID types.HexBytes `json:"electionId" `
OrganizationID types.HexBytes `json:"organizationId" `
Expand All @@ -37,6 +100,12 @@ type ElectionSummary struct {
ChainID string `json:"chainId"`
}

// ElectionsList is used to return a paginated list to the client
type ElectionsList struct {
Elections []ElectionSummary `json:"elections"`
Pagination *Pagination `json:"pagination"`
}

// ElectionResults is the struct used to wrap the results of an election
type ElectionResults struct {
// ABIEncoded is the abi encoded election results
Expand Down Expand Up @@ -100,13 +169,6 @@ type ElectionDescription struct {
TempSIKs bool `json:"tempSIKs"`
}

type ElectionFilter struct {
OrganizationID types.HexBytes `json:"organizationId,omitempty" `
ElectionID types.HexBytes `json:"electionId,omitempty" `
WithResults *bool `json:"withResults,omitempty"`
Status string `json:"status,omitempty"`
}

type Key struct {
Index int `json:"index"`
Key types.HexBytes `json:"key" `
Expand All @@ -115,7 +177,9 @@ type Key struct {
type Vote struct {
TxPayload []byte `json:"txPayload,omitempty" extensions:"x-omitempty" swaggerignore:"true"`
TxHash types.HexBytes `json:"txHash,omitempty" extensions:"x-omitempty" `
VoteID types.HexBytes `json:"voteID,omitempty" extensions:"x-omitempty" `
// VoteID here produces a `voteID` over JSON that differs in casing from the rest of params and JSONs
// but is kept for backwards compatibility
VoteID types.HexBytes `json:"voteID,omitempty" extensions:"x-omitempty" `
// Sent only for encrypted elections (no results until the end)
EncryptionKeyIndexes []uint32 `json:"encryptionKeys,omitempty" extensions:"x-omitempty"`
// For encrypted elections this will be codified
Expand All @@ -131,6 +195,11 @@ type Vote struct {
Date *time.Time `json:"date,omitempty" extensions:"x-omitempty"`
}

type VotesList struct {
Votes []Vote `json:"votes"`
Pagination *Pagination `json:"pagination"`
}

type CensusTypeDescription struct {
Type string `json:"type"`
Size uint64 `json:"size"`
Expand Down Expand Up @@ -187,6 +256,19 @@ type TransactionMetadata struct {
Hash types.HexBytes `json:"transactionHash" `
}

// TransactionsList is used to return a paginated list to the client
type TransactionsList struct {
Transactions []indexertypes.Transaction `json:"transactions"`
Pagination *Pagination `json:"pagination"`
}

// FeesList is used to return a paginated list to the client
type FeesList struct {
Fees []indexertypes.TokenFeeMeta `json:"fees"`
Pagination *Pagination `json:"pagination"`
}

// TODO: this struct should be deprecated, why blockNumber instead of blockHeight??
type BlockTransactionsInfo struct {
BlockNumber uint64 `json:"blockNumber"`
TransactionsCount uint32 `json:"transactionCount"`
Expand Down Expand Up @@ -229,6 +311,11 @@ type Account struct {
SIK types.HexBytes `json:"sik"`
}

type AccountsList struct {
Accounts []indexertypes.Account `json:"accounts"`
Pagination *Pagination `json:"pagination"`
}

type AccountSet struct {
TxPayload []byte `json:"txPayload,omitempty" swaggerignore:"true"`
Metadata []byte `json:"metadata,omitempty" swaggerignore:"true"`
Expand All @@ -237,6 +324,8 @@ type AccountSet struct {
}

type Census struct {
// CensusID here produces a `censusID` over JSON that differs in casing from the rest of params and JSONs
// but is kept for backwards compatibility
CensusID types.HexBytes `json:"censusID,omitempty"`
Type string `json:"type,omitempty"`
Weight *types.BigInt `json:"weight,omitempty"`
Expand Down
34 changes: 19 additions & 15 deletions api/censuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func (a *API) censusCreateHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont
// @Accept json
// @Produce json
// @Security BasicAuth
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Param transaction body CensusParticipants true "PublicKey - weight array "
// @Success 200 "(empty body)"
// @Router /censuses/{censusId}/participants [post]
Expand Down Expand Up @@ -341,7 +341,7 @@ func (a *API) censusAddHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext
// @Tags Censuses
// @Accept json
// @Produce json
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 {object} object{census=string} "Census type "weighted", "zkweighted", "csp"
// @Router /censuses/{censusId}/type [get]
func (a *API) censusTypeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -376,7 +376,7 @@ func (a *API) censusTypeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
// @Tags Censuses
// @Accept json
// @Produce json
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 {object} object{root=string} "Merkle root of the census"
// @Router /censuses/{censusId}/root [get]
func (a *API) censusRootHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -414,7 +414,7 @@ func (a *API) censusRootHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
// @Accept json
// @Produce json
// @Security BasicAuth
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 {object} censusdb.CensusDump
// @Router /censuses/{censusId}/export [get]
func (a *API) censusDumpHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -462,7 +462,7 @@ func (a *API) censusDumpHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContex
// @Accept json
// @Produce json
// @Security BasicAuth
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 "(empty body)"
// @Router /censuses/{censusId}/import [post]
func (a *API) censusImportHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -518,7 +518,7 @@ func (a *API) censusImportHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont
// @Tags Censuses
// @Accept json
// @Produce json
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 {object} object{weight=string} "Sum of weight son a stringfied big int format"
// @Router /censuses/{censusId}/weight [get]
func (a *API) censusWeightHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -555,7 +555,7 @@ func (a *API) censusWeightHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContex
// @Tags Censuses
// @Accept json
// @Produce json
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 {object} object{size=string} "Size as integer"
// @Router /censuses/{censusId}/size [get]
func (a *API) censusSizeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -595,7 +595,7 @@ func (a *API) censusSizeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
// @Tags Censuses
// @Accept json
// @Produce json
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 "(empty body)"
// @Router /censuses/{censusId} [delete]
func (a *API) censusDeleteHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -630,9 +630,11 @@ func (a *API) censusDeleteHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont
// @Produce json
// @Security BasicAuth
// @Success 200 {object} object{census=object{censusID=string,uri=string}} "It return published censusID and the ipfs uri where its uploaded"
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Param root path string false "Specific root where to publish the census. Not required"
// @Router /censuses/{censusId}/publish [post]
// @Router /censuses/{censusId}/publish/async [post]
// @Router /censuses/{censusId}/publish/{root} [post]
func (a *API) censusPublishHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
token, err := uuid.Parse(msg.AuthToken)
if err != nil {
Expand Down Expand Up @@ -780,7 +782,7 @@ func (a *API) censusPublishHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCon
// @Tags Censuses
// @Produce json
// @Success 200 {object} object{census=object{censusID=string,uri=string}} "It return published censusID and the ipfs uri where its uploaded"
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Router /censuses/{censusId}/check [get]
func (a *API) censusPublishCheckHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
censusID, err := censusIDparse(ctx.URLParam(ParamCensusId))
Expand Down Expand Up @@ -817,7 +819,7 @@ func (a *API) censusPublishCheckHandler(_ *apirest.APIdata, ctx *httprouter.HTTP
// @Accept json
// @Produce json
// @Security BasicAuth
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Param key path string true "Key to proof"
// @Success 200 {object} object{weight=number,proof=string,value=string} "where proof is Merkle tree siblings and value is Merkle tree leaf value"
// @Router /censuses/{censusId}/proof/{key} [get]
Expand Down Expand Up @@ -892,7 +894,7 @@ func (a *API) censusProofHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext
// @Tags Censuses
// @Accept json
// @Produce json
// @Param censusID path string true "Census id"
// @Param censusId path string true "Census id"
// @Success 200 {object} object{valid=bool}
// @Router /censuses/{censusId}/verify [post]
func (a *API) censusVerifyHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
Expand Down Expand Up @@ -957,7 +959,7 @@ func (a *API) censusVerifyHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont
// @Accept json
// @Produce json
// @Success 200 {object} object{valid=bool}
// @Router /censuses/list/ [get]
// @Router /censuses/list [get]
func (a *API) censusListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
list, err := a.censusdb.List()
if err != nil {
Expand All @@ -979,7 +981,8 @@ func (a *API) censusListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
// @Produce json
// @Param ipfs path string true "Export to IPFS. Blank to return the JSON file"
// @Success 200 {object} object{valid=bool}
// @Router /censuses/export/{ipfs} [get]
// @Router /censuses/export/ipfs [get]
// @Router /censuses/export [get]
func (a *API) censusExportDBHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
isIPFSExport := strings.HasSuffix(ctx.Request.URL.Path, "ipfs")
buf := bytes.Buffer{}
Expand Down Expand Up @@ -1012,7 +1015,8 @@ func (a *API) censusExportDBHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCont
// @Accept json
// @Produce json
// @Success 200 {object} object{valid=bool}
// @Router /censuses/import/{ipfscid} [post]
// @Router /censuses/import/{ipfscid} [get]
// @Router /censuses/import [post]
func (a *API) censusImportDBHandler(msg *apirest.APIdata, ctx *httprouter.HTTPContext) error {
ipfscid := ctx.URLParam("ipfscid")
if ipfscid == "" {
Expand Down
Loading

0 comments on commit f50c206

Please sign in to comment.