Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New /head endpoint for information about head block in database #18

Merged
merged 3 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
}
}
}
}
Loading