diff --git a/apiserver/controllers/block.go b/apiserver/controllers/block.go index f8862b90d..6e4dde6c4 100644 --- a/apiserver/controllers/block.go +++ b/apiserver/controllers/block.go @@ -11,6 +11,20 @@ import ( "github.com/daglabs/btcd/util/daghash" ) +const ( + // OrderAscending is parameter that can be used + // in a get list handler to get a list ordered + // in an ascending order. + OrderAscending = "asc" + + // OrderDescending is parameter that can be used + // in a get list handler to get a list ordered + // in an ascending order. + OrderDescending = "desc" +) + +const maxGetBlocksLimit = 100 + // GetBlockByHashHandler returns a block by a given hash. func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError) { if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize { @@ -30,3 +44,32 @@ func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError) } return convertBlockModelToBlockResponse(block), nil } + +// GetBlocksHandler searches for all blocks +func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) { + if limit > maxGetBlocksLimit { + return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit)) + } + blocks := []*models.Block{} + db, err := database.DB() + if err != nil { + return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + query := db. + Limit(limit). + Offset(skip). + Preload("AcceptingBlock") + if order == OrderAscending { + query = query.Order("`id` ASC") + } else if order == OrderDescending { + query = query.Order("`id` DESC") + } else { + return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid order", order)) + } + query.Find(&blocks) + blockResponses := make([]*blockResponse, len(blocks)) + for i, block := range blocks { + blockResponses[i] = convertBlockModelToBlockResponse(block) + } + return blockResponses, nil +} diff --git a/apiserver/controllers/transaction.go b/apiserver/controllers/transaction.go index 064c15964..31d3f30f3 100644 --- a/apiserver/controllers/transaction.go +++ b/apiserver/controllers/transaction.go @@ -12,7 +12,7 @@ import ( "github.com/jinzhu/gorm" ) -const maximumGetTransactionsLimit = 1000 +const maxGetTransactionsLimit = 1000 // GetTransactionByIDHandler returns a transaction by a given transaction ID. func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) { @@ -59,9 +59,9 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerErro // GetTransactionsByAddressHandler searches for all transactions // where the given address is either an input or an output. func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) { - if limit > maximumGetTransactionsLimit { + if limit > maxGetTransactionsLimit { return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, - fmt.Sprintf("The maximum allowed value for the limit is %d", maximumGetTransactionsLimit)) + fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit)) } db, err := database.DB() diff --git a/apiserver/server/routes.go b/apiserver/server/routes.go index b577913f3..42819f6e9 100644 --- a/apiserver/server/routes.go +++ b/apiserver/server/routes.go @@ -21,17 +21,27 @@ const ( const ( queryParamSkip = "skip" queryParamLimit = "limit" + queryParamOrder = "order" ) -const defaultGetTransactionsLimit = 100 +const ( + defaultGetTransactionsLimit = 100 + defaultGetBlocksLimit = 25 + defaultGetBlocksOrder = controllers.OrderAscending +) func makeHandler( - handler func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string][]string) ( + handler func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) ( interface{}, *utils.HandlerError)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { ctx := utils.ToAPIServerContext(r.Context()) - response, hErr := handler(ctx, mux.Vars(r), r.URL.Query()) + flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query()) + if hErr != nil { + sendErr(ctx, w, hErr) + return + } + response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams) if hErr != nil { sendErr(ctx, w, hErr) return @@ -40,6 +50,18 @@ func makeHandler( } } +func flattenQueryParams(queryParams map[string][]string) (map[string]string, *utils.HandlerError) { + flattenedMap := make(map[string]string) + for param, valuesSlice := range queryParams { + if len(valuesSlice) > 1 { + return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+ + " expected a single value but got multiple values", param)) + } + flattenedMap[param] = valuesSlice[0] + } + return flattenedMap, nil +} + type clientError struct { ErrorCode int `json:"errorCode"` ErrorMessage string `json:"errorMessage"` @@ -66,7 +88,7 @@ func sendJSONResponse(w http.ResponseWriter, response interface{}) { } } -func mainHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) { +func mainHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) { return "API server is running", nil } @@ -98,41 +120,48 @@ func addRoutes(router *mux.Router) { makeHandler(getBlockByHashHandler)). Methods("GET") + router.HandleFunc( + "/blocks", + makeHandler(getBlocksHandler)). + Methods("GET") + router.HandleFunc( "/fee-estimates", makeHandler(getFeeEstimatesHandler)). Methods("GET") } -func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) { +func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *utils.HandlerError) { + if _, ok := queryParams[param]; ok { + intValue, err := strconv.Atoi(queryParams[param]) + if err != nil { + return 0, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", param, err)) + } + return intValue, nil + } + return defaultValue, nil +} + +func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) { return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID]) } -func getTransactionByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) { +func getTransactionByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) { return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash]) } -func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string][]string) (interface{}, *utils.HandlerError) { - skip := 0 - limit := defaultGetTransactionsLimit - if len(queryParams[queryParamSkip]) > 1 { - return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+ - " expected a single value but got an array", queryParamSkip)) +func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) { + skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0) + if hErr != nil { + return nil, hErr } - if len(queryParams[queryParamSkip]) == 1 { + limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit) + if hErr != nil { + return nil, hErr + } + if _, ok := queryParams[queryParamLimit]; ok { var err error - skip, err = strconv.Atoi(queryParams[queryParamSkip][0]) - if err != nil { - return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamSkip, err)) - } - } - if len(queryParams[queryParamLimit]) > 1 { - return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+ - " expected a single value but got an array", queryParamLimit)) - } - if len(queryParams[queryParamLimit]) == 1 { - var err error - skip, err = strconv.Atoi(queryParams[queryParamLimit][0]) + skip, err = strconv.Atoi(queryParams[queryParamLimit]) if err != nil { return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err)) } @@ -140,14 +169,33 @@ func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[ return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit)) } -func getUTXOsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) { +func getUTXOsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) { return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress]) } -func getBlockByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) { +func getBlockByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) { return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash]) } -func getFeeEstimatesHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) { +func getFeeEstimatesHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) { return controllers.GetFeeEstimatesHandler() } + +func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) { + skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0) + if hErr != nil { + return nil, hErr + } + limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit) + if hErr != nil { + return nil, hErr + } + order := defaultGetBlocksOrder + if orderParamValue, ok := queryParams[queryParamOrder]; ok { + if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending { + return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit)) + } + order = orderParamValue + } + return controllers.GetBlocksHandler(order, uint64(skip), uint64(limit)) +}