Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/metal-breads-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensapi": patch
---

API file handlers rearrange, making them grouped by open api tags. Moving /amirealtime to /api/realtime and keeping old endpoint for backward compatibility.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { makeDurationSchema } from "@ensnode/ensnode-sdk/internal";

import { params } from "@/lib/handlers/params.schema";

export const basePath = "/amirealtime";
export const basePath = "/api/realtime";

// Set default `maxWorstCaseDistance` for `GET /amirealtime` endpoint to one minute.
export const AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE: Duration = minutesToSeconds(1);
// Set default `maxWorstCaseDistance` for `GET /api/realtime` endpoint to one minute.
export const REALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE: Duration = minutesToSeconds(1);

export const amIRealtimeGetMeta = createRoute({
export const realtimeGetMeta = createRoute({
method: "get",
path: "/",
operationId: "isRealtime",
operationId: "getRealtime",
tags: ["Meta"],
summary: "Check indexing progress",
description:
Expand All @@ -23,7 +23,7 @@ export const amIRealtimeGetMeta = createRoute({
query: z.object({
maxWorstCaseDistance: params.queryParam
.optional()
.default(AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE)
.default(REALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE)
.pipe(
z.coerce
.number({
Expand All @@ -47,4 +47,4 @@ export const amIRealtimeGetMeta = createRoute({
},
});

export const routes = [amIRealtimeGetMeta];
export const routes = [realtimeGetMeta];
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import {
import { createApp } from "@/lib/hono-factory";
import * as middleware from "@/middleware/indexing-status.middleware";

import amIRealtimeApi from "./amirealtime-api";
import { AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE } from "./amirealtime-api.routes";
import realtimeApi from "./realtime-api";
import { REALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE } from "./realtime-api.routes";

vi.mock("@/middleware/indexing-status.middleware", () => ({
indexingStatusMiddleware: vi.fn(),
}));

const indexingStatusMiddlewareMock = vi.mocked(middleware.indexingStatusMiddleware);

describe("amirealtime-api", () => {
describe("realtime-api", () => {
const now: UnixTimestamp = 1766123729;

const arrangeMockedIndexingStatusMiddleware = ({
Expand Down Expand Up @@ -51,21 +51,21 @@ describe("amirealtime-api", () => {
// Create a fresh app instance for each test with middleware registered
app = createApp();
app.use(middleware.indexingStatusMiddleware);
app.route("/amirealtime", amIRealtimeApi);
app.route("/realtime", realtimeApi);
});

afterEach(() => {
indexingStatusMiddlewareMock.mockReset();
});

describe("GET /amirealtime", () => {
describe("GET /realtime", () => {
describe("request", () => {
it("should accept valid maxWorstCaseDistance query param", async () => {
// Arrange: set `indexingStatus` context var
arrangeMockedIndexingStatusMiddleware({ now });

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=300");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=300");
const responseJson = await response.json();

// Assert
Expand All @@ -83,7 +83,7 @@ describe("amirealtime-api", () => {
});

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=0");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=0");
const responseJson = await response.json();

// Assert
Expand All @@ -98,13 +98,13 @@ describe("amirealtime-api", () => {
arrangeMockedIndexingStatusMiddleware({ now });

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance");
const responseJson = await response.json();

// Assert
expect(response.status).toBe(200);
expect(responseJson).toMatchObject({
maxWorstCaseDistance: AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE,
maxWorstCaseDistance: REALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE,
});
});

Expand All @@ -113,13 +113,13 @@ describe("amirealtime-api", () => {
arrangeMockedIndexingStatusMiddleware({ now });

// Act
const response = await app.request("http://localhost/amirealtime");
const response = await app.request("http://localhost/realtime");
const responseJson = await response.json();

// Assert
expect(response.status).toBe(200);
expect(responseJson).toMatchObject({
maxWorstCaseDistance: AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE,
maxWorstCaseDistance: REALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE,
});
});

Expand All @@ -128,7 +128,7 @@ describe("amirealtime-api", () => {
arrangeMockedIndexingStatusMiddleware({ now });

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=-1");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=-1");

// Assert
expect(response.status).toBe(400);
Expand All @@ -143,7 +143,7 @@ describe("amirealtime-api", () => {

// Act
const response = await app.request(
"http://localhost/amirealtime?maxWorstCaseDistance=invalid",
"http://localhost/realtime?maxWorstCaseDistance=invalid",
);

// Assert
Expand All @@ -163,7 +163,7 @@ describe("amirealtime-api", () => {
});

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=10");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=10");
const responseJson = await response.json();

// Assert
Expand All @@ -183,7 +183,7 @@ describe("amirealtime-api", () => {
});

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=10");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=10");
const responseJson = await response.json();

// Assert
Expand All @@ -203,7 +203,7 @@ describe("amirealtime-api", () => {
});

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=10");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=10");
const responseJson = await response.json();

// Assert
Expand All @@ -223,7 +223,7 @@ describe("amirealtime-api", () => {
});

// Act
const response = await app.request("http://localhost/amirealtime?maxWorstCaseDistance=10");
const response = await app.request("http://localhost/realtime?maxWorstCaseDistance=10");
const responseJson = await response.json();

// Assert
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { errorResponse } from "@/lib/handlers/error-response";
import { createApp } from "@/lib/hono-factory";

import { amIRealtimeGetMeta } from "./amirealtime-api.routes";
import { realtimeGetMeta } from "./realtime-api.routes";

const app = createApp();

// allow performance monitoring clients to read HTTP Status for the provided
// `maxWorstCaseDistance` param
app.openapi(amIRealtimeGetMeta, async (c) => {
app.openapi(realtimeGetMeta, async (c) => {
// context must be set by the required middleware
if (c.var.indexingStatus === undefined) {
throw new Error(`Invariant(amirealtime-api): indexingStatusMiddleware required.`);
throw new Error(`Invariant(realtime-api): indexingStatusMiddleware required.`);
}

// return 503 response error with details on prerequisite being unavailable
if (c.var.indexingStatus instanceof Error) {
return errorResponse(
c,
`Invariant(amirealtime-api): Indexing Status has to be resolved successfully before 'maxWorstCaseDistance' can be applied.`,
`Invariant(realtime-api): Indexing Status has to be resolved successfully before 'maxWorstCaseDistance' can be applied.`,
503,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import {
import { buildEnsApiPublicConfig } from "@/config/config.schema";
import { createApp } from "@/lib/hono-factory";

import { getConfigRoute, getIndexingStatusRoute } from "./ensnode-api.routes";
import ensnodeGraphQLApi from "./ensnode-graphql-api";
import nameTokensApi from "./name-tokens-api";
import registrarActionsApi from "./registrar-actions-api";
import resolutionApi from "./resolution-api";
import { getConfigRoute, getIndexingStatusRoute } from "./status-api.routes";

const app = createApp();

Expand Down Expand Up @@ -49,16 +45,4 @@ app.openapi(getIndexingStatusRoute, async (c) => {
);
});

// Name Tokens API
app.route("/name-tokens", nameTokensApi);

// Registrar Actions API
app.route("/registrar-actions", registrarActionsApi);

// Resolution API
app.route("/resolve", resolutionApi);

// ENSNode GraphQL API
app.route("/graphql", ensnodeGraphQLApi);

export default app;
19 changes: 19 additions & 0 deletions apps/ensapi/src/handlers/api/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createApp } from "@/lib/hono-factory";

import nameTokensApi from "./explore/name-tokens-api";
import registrarActionsApi from "./explore/registrar-actions-api";
import ensnodeGraphQLApi from "./graphql/ensnode-graphql-api";
import realtimeApi from "./meta/realtime-api";
import statusApi from "./meta/status-api";
import resolutionApi from "./resolution/resolution-api";

const app = createApp();

app.route("/", statusApi);
app.route("/realtime", realtimeApi);
app.route("/resolve", resolutionApi);
app.route("/name-tokens", nameTokensApi);
app.route("/registrar-actions", registrarActionsApi);
app.route("/graphql", ensnodeGraphQLApi);

export default app;
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { describe, expect, it, vi } from "vitest";
import { ENSNamespaceIds } from "@ensnode/datasources";

import type { EnsApiConfig } from "@/config/config.schema";

import * as editionsCachesMiddleware from "../middleware/referral-leaderboard-editions-caches.middleware";
import * as editionSetMiddleware from "../middleware/referral-program-edition-set.middleware";
import * as editionsCachesMiddleware from "@/middleware/referral-leaderboard-editions-caches.middleware";
import * as editionSetMiddleware from "@/middleware/referral-program-edition-set.middleware";

vi.mock("@/config", () => ({
get default() {
Expand All @@ -18,11 +17,11 @@ vi.mock("@/config", () => ({
},
}));

vi.mock("../middleware/referral-program-edition-set.middleware", () => ({
vi.mock("@/middleware/referral-program-edition-set.middleware", () => ({
referralProgramEditionConfigSetMiddleware: vi.fn(),
}));

vi.mock("../middleware/referral-leaderboard-editions-caches.middleware", () => ({
vi.mock("@/middleware/referral-leaderboard-editions-caches.middleware", () => ({
referralLeaderboardEditionsCachesMiddleware: vi.fn(),
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { describe, expect, it, vi } from "vitest";
import { ENSNamespaceIds } from "@ensnode/datasources";

import type { EnsApiConfig } from "@/config/config.schema";

import * as middleware from "../middleware/referrer-leaderboard.middleware";
import * as middleware from "@/middleware/referrer-leaderboard.middleware";

vi.mock("@/config", () => ({
get default() {
Expand All @@ -17,7 +16,7 @@ vi.mock("@/config", () => ({
},
}));

vi.mock("../middleware/referrer-leaderboard.middleware", () => ({
vi.mock("@/middleware/referrer-leaderboard.middleware", () => ({
referrerLeaderboardMiddleware: vi.fn(),
}));

Expand Down
15 changes: 8 additions & 7 deletions apps/ensapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import logger from "@/lib/logger";
import { indexingStatusMiddleware } from "@/middleware/indexing-status.middleware";
import { generateOpenApi31Document } from "@/openapi-document";

import amIRealtimeApi from "./handlers/amirealtime-api";
import ensanalyticsApi from "./handlers/ensanalytics-api";
import ensanalyticsApiV1 from "./handlers/ensanalytics-api-v1";
import ensNodeApi from "./handlers/ensnode-api";
import subgraphApi from "./handlers/subgraph-api";
import realtimeApi from "./handlers/api/meta/realtime-api";
import apiRouter from "./handlers/api/router";
import ensanalyticsApi from "./handlers/ensanalytics/ensanalytics-api";
import ensanalyticsApiV1 from "./handlers/ensanalytics/ensanalytics-api-v1";
import subgraphApi from "./handlers/subgraph/subgraph-api";

const app = factory.createApp();

Expand Down Expand Up @@ -60,7 +60,7 @@ app.get("/", (c) =>
);

// use ENSNode HTTP API at /api
app.route("/api", ensNodeApi);
app.route("/api", apiRouter);

// use Subgraph GraphQL API at /subgraph
app.route("/subgraph", subgraphApi);
Expand All @@ -72,7 +72,8 @@ app.route("/ensanalytics", ensanalyticsApi);
app.route("/v1/ensanalytics", ensanalyticsApiV1);

// use Am I Realtime API at /amirealtime
app.route("/amirealtime", amIRealtimeApi);
// NOTE: this is legacy endpoint and will be deleted in future. one should use /api/realtime instead
app.route("/amirealtime", realtimeApi);

// serve pre-generated OpenAPI 3.1 document
const openApi31Document = generateOpenApi31Document();
Expand Down
18 changes: 9 additions & 9 deletions apps/ensapi/src/openapi-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { OpenAPIHono } from "@hono/zod-openapi";

import { openapiMeta } from "@/openapi-meta";

import * as amIRealtimeRoutes from "./handlers/amirealtime-api.routes";
import * as ensanalyticsRoutes from "./handlers/ensanalytics-api.routes";
import * as ensanalyticsV1Routes from "./handlers/ensanalytics-api-v1.routes";
import * as ensnodeRoutes from "./handlers/ensnode-api.routes";
import * as nameTokensRoutes from "./handlers/name-tokens-api.routes";
import * as registrarActionsRoutes from "./handlers/registrar-actions-api.routes";
import * as resolutionRoutes from "./handlers/resolution-api.routes";
import * as nameTokensRoutes from "./handlers/api/explore/name-tokens-api.routes";
import * as registrarActionsRoutes from "./handlers/api/explore/registrar-actions-api.routes";
import * as realtimeRoutes from "./handlers/api/meta/realtime-api.routes";
import * as statusRoutes from "./handlers/api/meta/status-api.routes";
import * as resolutionRoutes from "./handlers/api/resolution/resolution-api.routes";
import * as ensanalyticsRoutes from "./handlers/ensanalytics/ensanalytics-api.routes";
import * as ensanalyticsV1Routes from "./handlers/ensanalytics/ensanalytics-api-v1.routes";

const routeGroups = [
amIRealtimeRoutes,
ensnodeRoutes,
realtimeRoutes,
statusRoutes,
ensanalyticsV1Routes,
ensanalyticsRoutes,
nameTokensRoutes,
Expand Down
4 changes: 2 additions & 2 deletions docs/docs.ensnode.io/ensapi-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
],
"components": { "schemas": {}, "parameters": {} },
"paths": {
"/amirealtime": {
"/api/realtime": {
"get": {
"operationId": "isRealtime",
"operationId": "getRealtime",
"tags": ["Meta"],
"summary": "Check indexing progress",
"description": "Checks if the indexing progress is guaranteed to be within a requested worst-case distance of realtime",
Expand Down
Loading