diff --git a/README.md b/README.md
index 66278a4..185578b 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,8 @@
| GET
`application/json` | `/balance` | Antelope Tokens balance changes |
| GET
`application/json` | `/transfers` | Antelope Tokens transfers |
| GET
`text/plain` | `/health` | Performs health checks and checks if the database is accessible |
-| GET
`text/plain` | `/metrics` | [Prometheus](https://prometheus.io/) metrics |
+| GET
`application/json` | `/head` | Information about the current head block in the database |
+| GET
`text/plain` | `/metrics` | [Prometheus](https://prometheus.io/) metrics for the API |
| GET
`application/json` | `/openapi` | [OpenAPI](https://www.openapis.org/) specification |
| GET
`application/json` | `/version` | API version and commit hash |
diff --git a/src/fetch/GET.ts b/src/fetch/GET.ts
index 26dbc10..40f78dd 100644
--- a/src/fetch/GET.ts
+++ b/src/fetch/GET.ts
@@ -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";
@@ -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);
diff --git a/src/fetch/head.ts b/src/fetch/head.ts
new file mode 100644
index 0000000..0a56bac
--- /dev/null
+++ b/src/fetch/head.ts
@@ -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")
+ )
+ );
+}
\ No newline at end of file
diff --git a/src/fetch/openapi.ts b/src/fetch/openapi.ts
index a3623d6..2881d36 100644
--- a/src/fetch/openapi.ts
+++ b/src/fetch/openapi.ts
@@ -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));
@@ -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", {
diff --git a/src/fetch/utils.spec.ts b/src/fetch/utils.spec.ts
index d4eed9f..220ac6d 100644
--- a/src/fetch/utils.spec.ts
+++ b/src/fetch/utils.spec.ts
@@ -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 = {
- 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 = {
+ 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
@@ -35,6 +35,7 @@ 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);
@@ -42,4 +43,13 @@ test("addMetadata pagination", () => {
// 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();
});
\ No newline at end of file
diff --git a/src/fetch/utils.ts b/src/fetch/utils.ts
index be97426..5195632 100644
--- a/src/fetch/utils.ts
+++ b/src/fetch/utils.ts
@@ -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, req_limit: number, req_page: number) {
+export function addMetadata(response: Query, 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,
+ }
}
- }
}
\ No newline at end of file