Skip to content

Commit

Permalink
New /head endpoint for information about head block in database (#18)
Browse files Browse the repository at this point in the history
* Make pagination optional in metadata

* Add new endpoint `/head`

* Update README for new `/head` endpoint

Resolves #8
  • Loading branch information
0237h authored Apr 30, 2024
1 parent c258d6a commit 22582ee
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 26 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
| GET <br>`application/json` | `/balance` | Antelope Tokens balance changes |
| GET <br>`application/json` | `/transfers` | Antelope Tokens transfers |
| GET <br>`text/plain` | `/health` | Performs health checks and checks if the database is accessible |
| GET <br>`text/plain` | `/metrics` | [Prometheus](https://prometheus.io/) metrics |
| GET <br>`application/json` | `/head` | Information about the current head block in the database |
| GET <br>`text/plain` | `/metrics` | [Prometheus](https://prometheus.io/) metrics for the API |
| GET <br>`application/json` | `/openapi` | [OpenAPI](https://www.openapis.org/) specification |
| GET <br>`application/json` | `/version` | API version and commit hash |

Expand Down
2 changes: 2 additions & 0 deletions src/fetch/GET.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { registry } from "../prometheus.js";
import openapi from "./openapi.js";
import health from "./health.js";
import head from "./head.js";
import balance from "./balance.js";
import supply from "./supply.js";
import * as prometheus from "../prometheus.js";
Expand All @@ -26,6 +27,7 @@ export default async function (req: Request) {
if (pathname === "/version") return toJSON({ version: APP_VERSION.split('+')[0], commit: APP_VERSION.split('+')[1] });

// Token endpoints
if (pathname === "/head") return head(req);
if (pathname === "/supply") return supply(req);
if (pathname === "/balance") return balance(req);
if (pathname === "/transfers") return transfers(req);
Expand Down
10 changes: 10 additions & 0 deletions src/fetch/head.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { addMetadata, toJSON } from "./utils.js";
import { makeQuery } from "../clickhouse/makeQuery.js";

export default async function (req: Request) {
return toJSON(
addMetadata(
await makeQuery("SELECT block_num FROM cursors ORDER BY block_num DESC LIMIT 1")
)
);
}
22 changes: 20 additions & 2 deletions src/fetch/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ const timestampExamplesArrayFilter = ["greater_or_equals_by_timestamp", "greater
const blockExamplesArrayFilter = ["greater_or_equals_by_block", "greater_by_block", "less_or_equals_by_block", "less_by_block"];
const amountExamplesArrayFilter = ["amount_greater_or_equals", "amount_greater", "amount_less_or_equals", "amount_less"];

const head_example = addMetadata({
meta: [],
data: [{ block_num: "107439534" }],
rows: 0,
rows_before_limit_at_least: 0,
statistics: {
elapsed: 0.00132,
rows_read: 4,
bytes_read: 32
}
});
const supply_example = await makeQuery(getTotalSupply(new URLSearchParams({ limit: "1" }), true)).then(res => addMetadata(res, 1, 1));
const balance_example = await makeQuery(getBalanceChanges(new URLSearchParams({ limit: "2" }), true)).then(res => addMetadata(res, 2, 1));
const transfers_example = await makeQuery(getTransfers(new URLSearchParams({ limit: "5" }), true)).then(res => addMetadata(res, 5, 1));
Expand Down Expand Up @@ -164,11 +175,18 @@ export default new OpenApiBuilder()
responses: { 200: { description: "OK", content: { "text/plain": { example: "OK" } } } },
},
})
.addPath("/head", {
get: {
tags: [TAGS.MONITORING],
summary: "Information about the current head block in the database",
responses: { 200: { description: "Information about the current head block in the database", content: { "application/json": { example: head_example } } } },
},
})
.addPath("/metrics", {
get: {
tags: [TAGS.MONITORING],
summary: "Prometheus metrics",
responses: { 200: { description: "Prometheus metrics", content: { "text/plain": { example: await registry.metrics(), schema: { type: "string" } } } } },
summary: "Prometheus metrics for the API",
responses: { 200: { description: "Prometheus metrics for the API", content: { "text/plain": { example: await registry.metrics(), schema: { type: "string" } } } } },
},
})
.addPath("/openapi", {
Expand Down
36 changes: 23 additions & 13 deletions src/fetch/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import { expect, test } from "bun:test";
import { addMetadata } from "./utils.js";
import { Query } from "../clickhouse/makeQuery.js";

test("addMetadata pagination", () => {
const limit = 5;
const mock_query_reponse: Query<any> = {
meta: [],
data: Array(limit),
rows: limit,
rows_before_limit_at_least: 5*limit, // Simulate query with more total results than the query limit making pagination relevant
statistics: {
elapsed: 0,
rows_read: 0,
bytes_read: 0,
}
};
const limit = 5;
const mock_query_reponse: Query<any> = {
meta: [],
data: Array(limit),
rows: limit,
rows_before_limit_at_least: 5*limit, // Simulate query with more total results than the query limit making pagination relevant
statistics: {
elapsed: 0,
rows_read: 0,
bytes_read: 0,
}
};

test("addMetadata pagination", () => {
const first_page = addMetadata(mock_query_reponse, limit, 1);
expect(first_page.meta.next_page).toBe(2);
expect(first_page.meta.previous_page).toBe(1); // Previous page should be set to 1 on first page
Expand All @@ -35,11 +35,21 @@ test("addMetadata pagination", () => {
expect(even_page.meta.total_results).toBe(5*limit);

const last_page = addMetadata(mock_query_reponse, limit, 5);
// @ts-ignore
expect(last_page.meta.next_page).toBe(last_page.meta.total_pages); // Next page should be capped to total_pages on last page
expect(last_page.meta.previous_page).toBe(4);
expect(last_page.meta.total_pages).toBe(5);
expect(last_page.meta.total_results).toBe(5*limit);

// TODO: Expect error message on beyond last page
// const beyond_last_page = addMetadata(mock_query_reponse.data, mock_query_reponse.rows_before_limit_at_least, limit, 6);
});

test("addMetadata no pagination", () => {
const no_pagination = addMetadata(mock_query_reponse);

expect(no_pagination.meta.next_page).toBeUndefined();
expect(no_pagination.meta.previous_page).toBeUndefined();
expect(no_pagination.meta.total_pages).toBeUndefined();
expect(no_pagination.meta.total_results).toBeUndefined();
});
28 changes: 18 additions & 10 deletions src/fetch/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@ export function toJSON(data: any, status: number = 200) {
return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } });
}

export function addMetadata(response: Query<any>, req_limit: number, req_page: number) {
export function addMetadata(response: Query<any>, req_limit?: number, req_page?: number) {
// TODO: Catch page number greater than total_pages and return error
return {
data: response.data,
meta: {
statistics: response.statistics,
"next_page": (req_page * req_limit >= response.rows_before_limit_at_least) ? req_page : req_page + 1,
"previous_page": (req_page <= 1) ? req_page : req_page - 1,
"total_pages": Math.ceil( response.rows_before_limit_at_least / req_limit),
"total_results": response.rows_before_limit_at_least
if (typeof(req_limit) !== 'undefined' && typeof(req_page) !== 'undefined')
return {
data: response.data,
meta: {
statistics: response.statistics,
"next_page": (req_page * req_limit >= response.rows_before_limit_at_least) ? req_page : req_page + 1,
"previous_page": (req_page <= 1) ? req_page : req_page - 1,
"total_pages": Math.ceil( response.rows_before_limit_at_least / req_limit),
"total_results": response.rows_before_limit_at_least
}
}
else
return {
data: response.data,
meta: {
statistics: response.statistics,
}
}
}
}

0 comments on commit 22582ee

Please sign in to comment.