From 37538c68ff6091f9e0f5dc28ee056e3c708775ba Mon Sep 17 00:00:00 2001 From: Mohammed Sohail Date: Thu, 19 May 2022 16:24:26 +0300 Subject: [PATCH] add (feat) pagination helpers and token endpoint - see docs for pagination api usage - added control for syncer/api goroutines --- cmd/init.go | 6 ++++ cmd/main.go | 44 +++++++++++++----------- config.toml | 6 ++++ internal/public/api.go | 4 +++ internal/public/tokens.go | 65 ++++++++++++++++++++++++++++++++++++ pkg/pagination/pagination.go | 34 +++++++++++++++++++ queries/public.sql | 13 ++++++++ 7 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 internal/public/tokens.go create mode 100644 pkg/pagination/pagination.go diff --git a/cmd/init.go b/cmd/init.go index c62c3f6..c34f0e1 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -33,6 +33,12 @@ type config struct { TokenRegistry string `koanf:"index"` BalanceResolver string `koanf:"balances_resolver"` } + Syncer struct { + Enabled bool `koanf:"enabled"` + } + Api struct { + Enabled bool `koan:"enabled"` + } Syncers map[string]string `koanf:"syncers"` } diff --git a/cmd/main.go b/cmd/main.go index 780d91e..c61d2d3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -70,29 +70,35 @@ func main() { log.Fatal().Err(err).Msg("could not bootstrap scheduler") } - go func() { - if err := scheduler.Run(); err != nil { - log.Fatal().Err(err).Msg("could not start scheduler") - } - }() - processor, mux := bootstrapProcessor(rClient) - go func() { - if err := processor.Run(mux); err != nil { - log.Fatal().Err(err).Msg("failed to start job processor") - } - }() + + if conf.Syncer.Enabled { + go func() { + if err := scheduler.Run(); err != nil { + log.Fatal().Err(err).Msg("could not start scheduler") + } + }() + + go func() { + if err := processor.Run(mux); err != nil { + log.Fatal().Err(err).Msg("failed to start job processor") + } + }() + } server := initHTTPServer() - go func() { - if err := server.Start(conf.Server.Address); err != nil { - if strings.Contains(err.Error(), "Server closed") { - log.Info().Msg("shutting down server") - } else { - log.Fatal().Err(err).Msg("could not start server") + + if conf.Api.Enabled { + go func() { + if err := server.Start(conf.Server.Address); err != nil { + if strings.Contains(err.Error(), "Server closed") { + log.Info().Msg("shutting down server") + } else { + log.Fatal().Err(err).Msg("could not start server") + } } - } - }() + }() + } sigs := make(chan os.Signal, 1) signal.Notify(sigs, unix.SIGTERM, unix.SIGINT, unix.SIGTSTP) diff --git a/config.toml b/config.toml index 7726531..5bb33cb 100644 --- a/config.toml +++ b/config.toml @@ -16,6 +16,12 @@ index = "0x5A1EB529438D8b3cA943A45a48744f4c73d1f098" balances_resolver = "0xb9e215B789e9Ec6643Ba4ff7b98EA219F38c6fE5" rpc = "http://127.0.0.1:8545" +[syncer] +enabled = true + +[api] +enabled = true + [syncers] cache = "@every 20s" ussd = "@every 30s" diff --git a/internal/public/api.go b/internal/public/api.go index c53fd30..81619b5 100644 --- a/internal/public/api.go +++ b/internal/public/api.go @@ -27,5 +27,9 @@ func InitPublicApi(e *echo.Echo, db *pgxpool.Pool, batchBalance *batch_balance.B } }) + // TODO: paginate schema validation + g.GET("/balances/:address", handleBalancesQuery) + g.GET("/tokens-count", handleTokensCountQuery) + g.GET("/tokens", handleTokenListQuery) } diff --git a/internal/public/tokens.go b/internal/public/tokens.go new file mode 100644 index 0000000..33328df --- /dev/null +++ b/internal/public/tokens.go @@ -0,0 +1,65 @@ +package public + +import ( + "cic-dw/pkg/pagination" + "context" + "net/http" + + "github.com/georgysavva/scany/pgxscan" + "github.com/labstack/echo/v4" +) + +type tokensRes struct { + Id int `db:"id" json:"id"` + TokenSymbol string `db:"token_symbol" json:"token_symbol"` + TokenName string `db:"token_name" json:"token_name"` + TokenAddress string `db:"token_address" json:"token_addres"` +} + +type tokenCountRes struct { + Count int `db:"count" json:"count"` +} + +func handleTokenListQuery(c echo.Context) error { + var ( + api = c.Get("api").(*api) + pg = pagination.GetPagination(c.QueryParams()) + res []tokensRes + q string + ) + + if pg.Forward { + q = api.q["list-tokens-fwd"] + } else { + q = api.q["list-tokens-bkwd"] + } + + rows, err := api.db.Query(context.Background(), q, pg.Cursor, pg.PerPage) + if err != nil { + return err + } + + if err := pgxscan.ScanAll(&res, rows); err != nil { + return err + } + + return c.JSON(http.StatusOK, res) +} + +func handleTokensCountQuery(c echo.Context) error { + var ( + api = c.Get("api").(*api) + res tokenCountRes + ) + + rows, err := api.db.Query(context.Background(), api.q["tokens-count"]) + if err != nil { + return err + } + + if err := pgxscan.ScanOne(&res, rows); err != nil { + return err + } + + return c.JSON(http.StatusOK, res) +} diff --git a/pkg/pagination/pagination.go b/pkg/pagination/pagination.go new file mode 100644 index 0000000..80a2a95 --- /dev/null +++ b/pkg/pagination/pagination.go @@ -0,0 +1,34 @@ +package pagination + +import ( + "net/url" + "strconv" +) + +type Pagination struct { + PerPage int + Cursor int + Forward bool +} + +func GetPagination(q url.Values) Pagination { + var ( + pp, _ = strconv.Atoi(q.Get("per_page")) + cursor, _ = strconv.Atoi(q.Get("cursor")) + forward, _ = strconv.ParseBool(q.Get("forward")) + ) + + if pp > 100 { + pp = 100 + } + + if !forward && cursor < 1 { + cursor = 1 + } + + return Pagination{ + PerPage: pp, + Cursor: cursor, + Forward: forward, + } +} diff --git a/queries/public.sql b/queries/public.sql index 7946e98..b4223cf 100644 --- a/queries/public.sql +++ b/queries/public.sql @@ -4,3 +4,16 @@ SELECT DISTINCT tokens.token_symbol, tokens.token_address FROM transactions INNER JOIN tokens on transactions.token_address = tokens.token_address WHERE transactions.sender_address = $1 OR transactions.recipient_address = $1; + +-- Bidirectional cursor pagianators +-- name: list-tokens-fwd +SELECT tokens.id, tokens.token_address, tokens.token_name, tokens.token_symbol FROM tokens +WHERE tokens.id > $1 ORDER BY tokens.id ASC LIMIT $2; + +-- name: list-tokens-bkwd +SELECT tokens.id, tokens.token_address, tokens.token_name, tokens.token_symbol FROM tokens +WHERE tokens.id < $1 ORDER BY tokens.id ASC LIMIT $2; + +-- name: tokens-count +-- Return total record count from individual i= tables/views +SELECT COUNT(*) FROM tokens; \ No newline at end of file