diff --git a/.changeset/metal-breads-bathe.md b/.changeset/metal-breads-bathe.md new file mode 100644 index 000000000..17fc9acfc --- /dev/null +++ b/.changeset/metal-breads-bathe.md @@ -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. diff --git a/apps/ensapi/src/handlers/name-tokens-api.routes.ts b/apps/ensapi/src/handlers/api/explore/name-tokens-api.routes.ts similarity index 100% rename from apps/ensapi/src/handlers/name-tokens-api.routes.ts rename to apps/ensapi/src/handlers/api/explore/name-tokens-api.routes.ts diff --git a/apps/ensapi/src/handlers/name-tokens-api.ts b/apps/ensapi/src/handlers/api/explore/name-tokens-api.ts similarity index 100% rename from apps/ensapi/src/handlers/name-tokens-api.ts rename to apps/ensapi/src/handlers/api/explore/name-tokens-api.ts diff --git a/apps/ensapi/src/handlers/registrar-actions-api.routes.ts b/apps/ensapi/src/handlers/api/explore/registrar-actions-api.routes.ts similarity index 100% rename from apps/ensapi/src/handlers/registrar-actions-api.routes.ts rename to apps/ensapi/src/handlers/api/explore/registrar-actions-api.routes.ts diff --git a/apps/ensapi/src/handlers/registrar-actions-api.ts b/apps/ensapi/src/handlers/api/explore/registrar-actions-api.ts similarity index 100% rename from apps/ensapi/src/handlers/registrar-actions-api.ts rename to apps/ensapi/src/handlers/api/explore/registrar-actions-api.ts diff --git a/apps/ensapi/src/handlers/ensnode-graphql-api.ts b/apps/ensapi/src/handlers/api/graphql/ensnode-graphql-api.ts similarity index 100% rename from apps/ensapi/src/handlers/ensnode-graphql-api.ts rename to apps/ensapi/src/handlers/api/graphql/ensnode-graphql-api.ts diff --git a/apps/ensapi/src/handlers/amirealtime-api.routes.ts b/apps/ensapi/src/handlers/api/meta/realtime-api.routes.ts similarity index 76% rename from apps/ensapi/src/handlers/amirealtime-api.routes.ts rename to apps/ensapi/src/handlers/api/meta/realtime-api.routes.ts index 328b94167..56fd2f541 100644 --- a/apps/ensapi/src/handlers/amirealtime-api.routes.ts +++ b/apps/ensapi/src/handlers/api/meta/realtime-api.routes.ts @@ -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: @@ -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({ @@ -47,4 +47,4 @@ export const amIRealtimeGetMeta = createRoute({ }, }); -export const routes = [amIRealtimeGetMeta]; +export const routes = [realtimeGetMeta]; diff --git a/apps/ensapi/src/handlers/amirealtime-api.test.ts b/apps/ensapi/src/handlers/api/meta/realtime-api.test.ts similarity index 83% rename from apps/ensapi/src/handlers/amirealtime-api.test.ts rename to apps/ensapi/src/handlers/api/meta/realtime-api.test.ts index 8d807c24e..e59bd6458 100644 --- a/apps/ensapi/src/handlers/amirealtime-api.test.ts +++ b/apps/ensapi/src/handlers/api/meta/realtime-api.test.ts @@ -9,8 +9,8 @@ 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(), @@ -18,7 +18,7 @@ vi.mock("@/middleware/indexing-status.middleware", () => ({ const indexingStatusMiddlewareMock = vi.mocked(middleware.indexingStatusMiddleware); -describe("amirealtime-api", () => { +describe("realtime-api", () => { const now: UnixTimestamp = 1766123729; const arrangeMockedIndexingStatusMiddleware = ({ @@ -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 @@ -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 @@ -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, }); }); @@ -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, }); }); @@ -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); @@ -143,7 +143,7 @@ describe("amirealtime-api", () => { // Act const response = await app.request( - "http://localhost/amirealtime?maxWorstCaseDistance=invalid", + "http://localhost/realtime?maxWorstCaseDistance=invalid", ); // Assert @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/apps/ensapi/src/handlers/amirealtime-api.ts b/apps/ensapi/src/handlers/api/meta/realtime-api.ts similarity index 80% rename from apps/ensapi/src/handlers/amirealtime-api.ts rename to apps/ensapi/src/handlers/api/meta/realtime-api.ts index 7b45d33fb..6c222ca01 100644 --- a/apps/ensapi/src/handlers/amirealtime-api.ts +++ b/apps/ensapi/src/handlers/api/meta/realtime-api.ts @@ -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, ); } diff --git a/apps/ensapi/src/handlers/ensnode-api.routes.ts b/apps/ensapi/src/handlers/api/meta/status-api.routes.ts similarity index 100% rename from apps/ensapi/src/handlers/ensnode-api.routes.ts rename to apps/ensapi/src/handlers/api/meta/status-api.routes.ts diff --git a/apps/ensapi/src/handlers/ensnode-api.ts b/apps/ensapi/src/handlers/api/meta/status-api.ts similarity index 72% rename from apps/ensapi/src/handlers/ensnode-api.ts rename to apps/ensapi/src/handlers/api/meta/status-api.ts index 96e65d0c9..b295823b8 100644 --- a/apps/ensapi/src/handlers/ensnode-api.ts +++ b/apps/ensapi/src/handlers/api/meta/status-api.ts @@ -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(); @@ -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; diff --git a/apps/ensapi/src/handlers/resolution-api.routes.ts b/apps/ensapi/src/handlers/api/resolution/resolution-api.routes.ts similarity index 100% rename from apps/ensapi/src/handlers/resolution-api.routes.ts rename to apps/ensapi/src/handlers/api/resolution/resolution-api.routes.ts diff --git a/apps/ensapi/src/handlers/resolution-api.ts b/apps/ensapi/src/handlers/api/resolution/resolution-api.ts similarity index 100% rename from apps/ensapi/src/handlers/resolution-api.ts rename to apps/ensapi/src/handlers/api/resolution/resolution-api.ts diff --git a/apps/ensapi/src/handlers/api/router.ts b/apps/ensapi/src/handlers/api/router.ts new file mode 100644 index 000000000..1a8d31e54 --- /dev/null +++ b/apps/ensapi/src/handlers/api/router.ts @@ -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; diff --git a/apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.routes.ts similarity index 100% rename from apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts rename to apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.routes.ts diff --git a/apps/ensapi/src/handlers/ensanalytics-api-v1.test.ts b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.test.ts similarity index 99% rename from apps/ensapi/src/handlers/ensanalytics-api-v1.test.ts rename to apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.test.ts index 2984d054a..27b140055 100644 --- a/apps/ensapi/src/handlers/ensanalytics-api-v1.test.ts +++ b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.test.ts @@ -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() { @@ -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(), })); diff --git a/apps/ensapi/src/handlers/ensanalytics-api-v1.ts b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.ts similarity index 100% rename from apps/ensapi/src/handlers/ensanalytics-api-v1.ts rename to apps/ensapi/src/handlers/ensanalytics/ensanalytics-api-v1.ts diff --git a/apps/ensapi/src/handlers/ensanalytics-api.routes.ts b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.routes.ts similarity index 100% rename from apps/ensapi/src/handlers/ensanalytics-api.routes.ts rename to apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.routes.ts diff --git a/apps/ensapi/src/handlers/ensanalytics-api.test.ts b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts similarity index 98% rename from apps/ensapi/src/handlers/ensanalytics-api.test.ts rename to apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts index 0d0dcefc8..74582d1d7 100644 --- a/apps/ensapi/src/handlers/ensanalytics-api.test.ts +++ b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts @@ -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() { @@ -17,7 +16,7 @@ vi.mock("@/config", () => ({ }, })); -vi.mock("../middleware/referrer-leaderboard.middleware", () => ({ +vi.mock("@/middleware/referrer-leaderboard.middleware", () => ({ referrerLeaderboardMiddleware: vi.fn(), })); diff --git a/apps/ensapi/src/handlers/ensanalytics-api.ts b/apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts similarity index 100% rename from apps/ensapi/src/handlers/ensanalytics-api.ts rename to apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts diff --git a/apps/ensapi/src/handlers/subgraph-api.ts b/apps/ensapi/src/handlers/subgraph/subgraph-api.ts similarity index 100% rename from apps/ensapi/src/handlers/subgraph-api.ts rename to apps/ensapi/src/handlers/subgraph/subgraph-api.ts diff --git a/apps/ensapi/src/index.ts b/apps/ensapi/src/index.ts index 778ab816c..a5db69aeb 100644 --- a/apps/ensapi/src/index.ts +++ b/apps/ensapi/src/index.ts @@ -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(); @@ -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); @@ -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(); diff --git a/apps/ensapi/src/openapi-document.ts b/apps/ensapi/src/openapi-document.ts index 7a45968e0..ae6805800 100644 --- a/apps/ensapi/src/openapi-document.ts +++ b/apps/ensapi/src/openapi-document.ts @@ -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, diff --git a/docs/docs.ensnode.io/ensapi-openapi.json b/docs/docs.ensnode.io/ensapi-openapi.json index 54e2e9423..d69de2e6f 100644 --- a/docs/docs.ensnode.io/ensapi-openapi.json +++ b/docs/docs.ensnode.io/ensapi-openapi.json @@ -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",