From 3b5d23462f08bc1d3b07f5c5cac188296f4b6e28 Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Thu, 8 Aug 2024 14:54:52 +0200 Subject: [PATCH] api: endpoint /elections can now filter startDate and endDate this includes a refactor of parseElectionParams since the args list went wild --- api/accounts.go | 25 +++----- api/api.go | 36 ++++++------ api/api_types.go | 16 ++++-- api/elections.go | 82 ++++++++++++++------------- api/errors.go | 1 + api/helpers.go | 24 ++++++++ vochain/indexer/db/processes.sql.go | 14 +++++ vochain/indexer/queries/processes.sql | 6 ++ 8 files changed, 124 insertions(+), 80 deletions(-) diff --git a/api/accounts.go b/api/accounts.go index b75b08643..7de92ac00 100644 --- a/api/accounts.go +++ b/api/accounts.go @@ -355,15 +355,9 @@ func (a *API) accountCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContex // @Success 200 {object} ElectionsList // @Router /accounts/{organizationId}/elections/page/{page} [get] func (a *API) accountElectionsListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - params, err := parseElectionParams( - ctx.URLParam(ParamPage), - "", - "", - ctx.URLParam(ParamOrganizationId), - "", - "", - "", - "", + params, err := electionParams(ctx.URLParam, + ParamPage, + ParamOrganizationId, ) if err != nil { return err @@ -391,15 +385,10 @@ func (a *API) accountElectionsListByPageHandler(_ *apirest.APIdata, ctx *httprou // @Success 200 {object} ElectionsList // @Router /accounts/{organizationId}/elections/status/{status}/page/{page} [get] func (a *API) accountElectionsListByStatusAndPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - params, err := parseElectionParams( - ctx.URLParam(ParamPage), - "", - ctx.URLParam(ParamStatus), - ctx.URLParam(ParamOrganizationId), - "", - "", - "", - "", + params, err := electionParams(ctx.URLParam, + ParamPage, + ParamStatus, + ParamOrganizationId, ) if err != nil { return err diff --git a/api/api.go b/api/api.go index 773b0ff8b..6dcacc5f6 100644 --- a/api/api.go +++ b/api/api.go @@ -60,22 +60,26 @@ const ( // //nolint:revive const ( - ParamAccountId = "accountId" - ParamCensusId = "censusId" - ParamElectionId = "electionId" - ParamOrganizationId = "organizationId" - ParamVoteId = "voteId" - ParamPage = "page" - ParamLimit = "limit" - ParamStatus = "status" - ParamWithResults = "withResults" - ParamFinalResults = "finalResults" - ParamManuallyEnded = "manuallyEnded" - ParamHeight = "height" - ParamReference = "reference" - ParamType = "type" - ParamAccountIdFrom = "accountIdFrom" - ParamAccountIdTo = "accountIdTo" + ParamAccountId = "accountId" + ParamCensusId = "censusId" + ParamElectionId = "electionId" + ParamOrganizationId = "organizationId" + ParamVoteId = "voteId" + ParamPage = "page" + ParamLimit = "limit" + ParamStatus = "status" + ParamWithResults = "withResults" + ParamFinalResults = "finalResults" + ParamManuallyEnded = "manuallyEnded" + ParamHeight = "height" + ParamReference = "reference" + ParamType = "type" + ParamAccountIdFrom = "accountIdFrom" + ParamAccountIdTo = "accountIdTo" + ParamStartDateAfter = "startDateAfter" + ParamStartDateBefore = "startDateBefore" + ParamEndDateAfter = "endDateAfter" + ParamEndDateBefore = "endDateBefore" ) var ( diff --git a/api/api_types.go b/api/api_types.go index e3b3ddd1c..d948b7207 100644 --- a/api/api_types.go +++ b/api/api_types.go @@ -23,12 +23,16 @@ type PaginationParams struct { // ElectionParams allows the client to filter elections type ElectionParams struct { PaginationParams - OrganizationID string `json:"organizationId,omitempty"` - ElectionID string `json:"electionId,omitempty"` - Status string `json:"status,omitempty"` - WithResults *bool `json:"withResults,omitempty"` - FinalResults *bool `json:"finalResults,omitempty"` - ManuallyEnded *bool `json:"manuallyEnded,omitempty"` + OrganizationID string `json:"organizationId,omitempty"` + ElectionID string `json:"electionId,omitempty"` + Status string `json:"status,omitempty"` + WithResults *bool `json:"withResults,omitempty"` + FinalResults *bool `json:"finalResults,omitempty"` + ManuallyEnded *bool `json:"manuallyEnded,omitempty"` + StartDateAfter *time.Time `json:"startDateAfter,omitempty"` + StartDateBefore *time.Time `json:"startDateBefore,omitempty"` + EndDateAfter *time.Time `json:"endDateAfter,omitempty"` + EndDateBefore *time.Time `json:"endDateBefore,omitempty"` } // OrganizationParams allows the client to filter organizations diff --git a/api/elections.go b/api/elections.go index 59da95f68..8074e3e53 100644 --- a/api/elections.go +++ b/api/elections.go @@ -207,15 +207,8 @@ func (a *API) electionListByFilterHandler(msg *apirest.APIdata, ctx *httprouter. // @Success 200 {object} ElectionsList // @Router /elections/page/{page} [get] func (a *API) electionListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - params, err := parseElectionParams( - ctx.URLParam(ParamPage), - "", - "", - "", - "", - "", - "", - "", + params, err := electionParams(ctx.URLParam, + ParamPage, ) if err != nil { return err @@ -241,15 +234,19 @@ func (a *API) electionListByPageHandler(_ *apirest.APIdata, ctx *httprouter.HTTP // @Success 200 {object} ElectionsList // @Router /elections [get] func (a *API) electionListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - params, err := parseElectionParams( - ctx.QueryParam(ParamPage), - ctx.QueryParam(ParamLimit), - ctx.QueryParam(ParamStatus), - ctx.QueryParam(ParamOrganizationId), - ctx.QueryParam(ParamElectionId), - ctx.QueryParam(ParamWithResults), - ctx.QueryParam(ParamFinalResults), - ctx.QueryParam(ParamManuallyEnded), + params, err := electionParams(ctx.QueryParam, + ParamPage, + ParamLimit, + ParamStatus, + ParamOrganizationId, + ParamElectionId, + ParamWithResults, + ParamFinalResults, + ParamManuallyEnded, + ParamStartDateAfter, + ParamStartDateBefore, + ParamEndDateAfter, + ParamEndDateBefore, ) if err != nil { return err @@ -757,38 +754,43 @@ func (a *API) buildElectionIDHandler(msg *apirest.APIdata, ctx *httprouter.HTTPC return ctx.Send(data, apirest.HTTPstatusOK) } -// parseElectionParams returns an ElectionParams filled with the passed params -func parseElectionParams(paramPage, paramLimit, paramStatus, - paramOrganizationID, paramElectionID, - paramWithResults, paramFinalResults, paramManuallyEnded string, -) (*ElectionParams, error) { - pagination, err := parsePaginationParams(paramPage, paramLimit) - if err != nil { - return nil, err - } +// electionParams produces an ElectionParams, calling the passed func (ctx.QueryParam or ctx.URLParam) +// to retrieve all the values of all keys passed. +func electionParams(f func(key string) string, keys ...string) (*ElectionParams, error) { + strings := paramsFromCtxFunc(f, keys...) - withResults, err := parseBool(paramWithResults) + pagination, err := parsePaginationParams(strings[ParamPage], strings[ParamLimit]) if err != nil { return nil, err } - finalResults, err := parseBool(paramFinalResults) - if err != nil { - return nil, err + bools := make(map[string]*bool) + for _, v := range []string{ParamWithResults, ParamFinalResults, ParamManuallyEnded} { + bools[v], err = parseBool(strings[v]) + if err != nil { + return nil, err + } } - manuallyEnded, err := parseBool(paramManuallyEnded) - if err != nil { - return nil, err + dates := make(map[string]*time.Time) + for _, v := range []string{ParamStartDateAfter, ParamStartDateBefore, ParamEndDateAfter, ParamEndDateBefore} { + dates[v], err = parseDate(strings[v]) + if err != nil { + return nil, err + } } return &ElectionParams{ PaginationParams: pagination, - OrganizationID: util.TrimHex(paramOrganizationID), - ElectionID: util.TrimHex(paramElectionID), - Status: paramStatus, - WithResults: withResults, - FinalResults: finalResults, - ManuallyEnded: manuallyEnded, + OrganizationID: util.TrimHex(strings[ParamOrganizationId]), + ElectionID: util.TrimHex(strings[ParamElectionId]), + Status: strings[ParamStatus], + WithResults: bools[ParamWithResults], + FinalResults: bools[ParamFinalResults], + ManuallyEnded: bools[ParamManuallyEnded], + StartDateAfter: dates[ParamStartDateAfter], + StartDateBefore: dates[ParamStartDateBefore], + EndDateAfter: dates[ParamEndDateAfter], + EndDateBefore: dates[ParamEndDateBefore], }, nil } diff --git a/api/errors.go b/api/errors.go index 3d4e4c31b..f6ed2af3c 100644 --- a/api/errors.go +++ b/api/errors.go @@ -83,6 +83,7 @@ var ( ErrCantParseBoolean = apirest.APIerror{Code: 4055, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse string into boolean")} ErrCantParseHexString = apirest.APIerror{Code: 4056, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse string into hex bytes")} ErrPageNotFound = apirest.APIerror{Code: 4057, HTTPstatus: apirest.HTTPstatusNotFound, Err: fmt.Errorf("page not found")} + ErrCantParseDate = apirest.APIerror{Code: 4058, HTTPstatus: apirest.HTTPstatusBadRequest, Err: fmt.Errorf("cannot parse date")} ErrVochainEmptyReply = apirest.APIerror{Code: 5000, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain returned an empty reply")} ErrVochainSendTxFailed = apirest.APIerror{Code: 5001, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain SendTx failed")} ErrVochainGetTxFailed = apirest.APIerror{Code: 5002, HTTPstatus: apirest.HTTPstatusInternalErr, Err: fmt.Errorf("vochain GetTx failed")} diff --git a/api/helpers.go b/api/helpers.go index b261cb901..1062ed245 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -9,6 +9,7 @@ import ( "math/big" "strconv" "strings" + "time" cometpool "github.com/cometbft/cometbft/mempool" cometcoretypes "github.com/cometbft/cometbft/rpc/core/types" @@ -275,6 +276,20 @@ func parseBool(s string) (*bool, error) { return &b, nil } +// parseDate parses an RFC3339 string into a time.Time value. +// +// The empty string "" is treated specially, returns a nil pointer with no error. +func parseDate(s string) (*time.Time, error) { + if s == "" { + return nil, nil + } + b, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, ErrCantParseDate.With(s) + } + return &b, nil +} + // parsePaginationParams returns a PaginationParams filled with the passed params func parsePaginationParams(paramPage, paramLimit string) (PaginationParams, error) { page, err := parsePage(paramPage) @@ -325,3 +340,12 @@ func calculatePagination(page int, limit int, totalItems uint64) (*Pagination, e LastPage: uint64(lastp), }, nil } + +// paramsFromCtxFunc calls f(key) for each key passed, and the resulting value is saved in map[key] of the returned map +func paramsFromCtxFunc(f func(key string) string, keys ...string) map[string]string { + m := make(map[string]string) + for _, key := range keys { + m[key] = f(key) + } + return m +} diff --git a/vochain/indexer/db/processes.sql.go b/vochain/indexer/db/processes.sql.go index 49a851b5a..2310892c6 100644 --- a/vochain/indexer/db/processes.sql.go +++ b/vochain/indexer/db/processes.sql.go @@ -305,6 +305,12 @@ WITH results AS ( OR (?10 = 1 AND manually_ended = TRUE) OR (?10 = 0 AND manually_ended = FALSE) ) + AND ( + (?11 IS NULL OR start_date >= ?11) + AND (?12 IS NULL OR start_date <= ?12) + AND (?13 IS NULL OR end_date >= ?13) + AND (?14 IS NULL OR end_date <= ?14) + ) ) ) SELECT id, total_count @@ -325,6 +331,10 @@ type SearchProcessesParams struct { HaveResults interface{} FinalResults interface{} ManuallyEnded interface{} + StartDateAfter interface{} + StartDateBefore interface{} + EndDateAfter interface{} + EndDateBefore interface{} } type SearchProcessesRow struct { @@ -344,6 +354,10 @@ func (q *Queries) SearchProcesses(ctx context.Context, arg SearchProcessesParams arg.HaveResults, arg.FinalResults, arg.ManuallyEnded, + arg.StartDateAfter, + arg.StartDateBefore, + arg.EndDateAfter, + arg.EndDateBefore, ) if err != nil { return nil, err diff --git a/vochain/indexer/queries/processes.sql b/vochain/indexer/queries/processes.sql index 2ee6499a9..8fe70195d 100644 --- a/vochain/indexer/queries/processes.sql +++ b/vochain/indexer/queries/processes.sql @@ -68,6 +68,12 @@ WITH results AS ( OR (sqlc.arg(manually_ended) = 1 AND manually_ended = TRUE) OR (sqlc.arg(manually_ended) = 0 AND manually_ended = FALSE) ) + AND ( + (sqlc.arg(start_date_after) IS NULL OR start_date >= sqlc.arg(start_date_after)) + AND (sqlc.arg(start_date_before) IS NULL OR start_date <= sqlc.arg(start_date_before)) + AND (sqlc.arg(end_date_after) IS NULL OR end_date >= sqlc.arg(end_date_after)) + AND (sqlc.arg(end_date_before) IS NULL OR end_date <= sqlc.arg(end_date_before)) + ) ) ) SELECT id, total_count