Skip to content
Open
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
110 changes: 63 additions & 47 deletions apps/ensapi/src/handlers/api/explore/registrar-actions-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { trace } from "@opentelemetry/api";

import {
buildPageContext,
type Node,
Expand All @@ -10,6 +12,7 @@ import {
} from "@ensnode/ensnode-sdk";

import { createApp } from "@/lib/hono-factory";
import { withActiveSpanAsync } from "@/lib/instrumentation/auto-span";
import { makeLogger } from "@/lib/logger";
import { findRegistrarActions } from "@/lib/registrar-actions/find-registrar-actions";
import { indexingStatusMiddleware } from "@/middleware/indexing-status.middleware";
Expand All @@ -24,56 +27,69 @@ import {
const app = createApp({ middlewares: [indexingStatusMiddleware, registrarActionsApiMiddleware] });

const logger = makeLogger("registrar-actions-api");
const tracer = trace.getTracer("registrar-actions-api");

// Shared business logic for fetching registrar actions
async function fetchRegistrarActions(parentNode: Node | undefined, query: RegistrarActionsQuery) {
const {
orderBy,
page,
recordsPerPage,
withReferral,
decodedReferrer,
beginTimestamp,
endTimestamp,
} = query;

const filters: RegistrarActionsFilter[] = [];

if (parentNode) {
filters.push(registrarActionsFilter.byParentNode(parentNode));
}

if (withReferral) {
filters.push(registrarActionsFilter.withReferral(true));
}

if (decodedReferrer) {
filters.push(registrarActionsFilter.byDecodedReferrer(decodedReferrer));
}

if (beginTimestamp) {
filters.push(registrarActionsFilter.beginTimestamp(beginTimestamp));
}

if (endTimestamp) {
filters.push(registrarActionsFilter.endTimestamp(endTimestamp));
}

// Calculate offset from page and recordsPerPage
const offset = (page - 1) * recordsPerPage;

// Find the latest "logical registrar actions" with pagination
const { registrarActions, totalRecords } = await findRegistrarActions({
filters,
orderBy,
limit: recordsPerPage,
offset,
});

// Build page context
const pageContext = buildPageContext(page, recordsPerPage, totalRecords);

return { registrarActions, pageContext };
return withActiveSpanAsync(
tracer,
"fetchRegistrarActions",
{
parentNode: parentNode ?? "undefined",
page: query.page,
recordsPerPage: query.recordsPerPage,
orderBy: query.orderBy,
},
async () => {
const {
orderBy,
page,
recordsPerPage,
withReferral,
decodedReferrer,
beginTimestamp,
endTimestamp,
} = query;

const filters: RegistrarActionsFilter[] = [];

if (parentNode) {
filters.push(registrarActionsFilter.byParentNode(parentNode));
}

if (withReferral) {
filters.push(registrarActionsFilter.withReferral(true));
}

if (decodedReferrer) {
filters.push(registrarActionsFilter.byDecodedReferrer(decodedReferrer));
}

if (beginTimestamp) {
filters.push(registrarActionsFilter.beginTimestamp(beginTimestamp));
}

if (endTimestamp) {
filters.push(registrarActionsFilter.endTimestamp(endTimestamp));
}

// Calculate offset from page and recordsPerPage
const offset = (page - 1) * recordsPerPage;

// Find the latest "logical registrar actions" with pagination
const { registrarActions, totalRecords } = await findRegistrarActions({
filters,
orderBy,
limit: recordsPerPage,
offset,
});

// Build page context
const pageContext = buildPageContext(page, recordsPerPage, totalRecords);

return { registrarActions, pageContext };
},
);
}

/**
Expand Down
132 changes: 70 additions & 62 deletions apps/ensapi/src/lib/registrar-actions/find-registrar-actions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { trace } from "@opentelemetry/api";
import { and, count, desc, eq, gte, isNotNull, lte, not, type SQL } from "drizzle-orm/sql";

import {
Expand All @@ -21,6 +22,9 @@ import {
} from "@ensnode/ensnode-sdk";

import { ensDb, ensIndexerSchema } from "@/lib/ensdb/singleton";
import { withSpanAsync } from "@/lib/instrumentation/auto-span";

const tracer = trace.getTracer("registrar-actions");

/**
* Build SQL for order clause from provided order param.
Expand Down Expand Up @@ -97,74 +101,78 @@ interface FindRegistrarActionsOptions {
export async function _countRegistrarActions(
filters: RegistrarActionsFilter[] | undefined,
): Promise<number> {
const countQuery = ensDb
.select({
count: count(),
})
.from(ensIndexerSchema.registrarActions)
// join Registration Lifecycles associated with Registrar Actions
.innerJoin(
ensIndexerSchema.registrationLifecycles,
eq(ensIndexerSchema.registrarActions.node, ensIndexerSchema.registrationLifecycles.node),
)
// join Domains associated with Registration Lifecycles
.innerJoin(
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removes the unnecessary join on domains, not needed for count

ensIndexerSchema.subgraph_domain,
eq(ensIndexerSchema.registrationLifecycles.node, ensIndexerSchema.subgraph_domain.id),
)
// join Subregistries associated with Registration Lifecycles
.innerJoin(
ensIndexerSchema.subregistries,
eq(
ensIndexerSchema.registrationLifecycles.subregistryId,
ensIndexerSchema.subregistries.subregistryId,
),
)
.where(and(...buildWhereClause(filters)));

const result = await countQuery;
return result[0].count;
return withSpanAsync(
tracer,
"registrarActions.count",
{ filterCount: filters?.length ?? 0 },
async () => {
const result = await ensDb
.select({ count: count() })
.from(ensIndexerSchema.registrarActions)
.innerJoin(
ensIndexerSchema.registrationLifecycles,
eq(ensIndexerSchema.registrarActions.node, ensIndexerSchema.registrationLifecycles.node),
)
.innerJoin(
ensIndexerSchema.subregistries,
eq(
ensIndexerSchema.registrationLifecycles.subregistryId,
ensIndexerSchema.subregistries.subregistryId,
),
)
.where(and(...buildWhereClause(filters)));

return result[0].count;
},
);
}

/**
* Internal function which executes a single query to get all data required to
* build a list of {@link NamedRegistrarAction} objects.
*/
export async function _findRegistrarActions(options: FindRegistrarActionsOptions) {
const query = ensDb
.select({
registrarActions: ensIndexerSchema.registrarActions,
registrationLifecycles: ensIndexerSchema.registrationLifecycles,
subregistries: ensIndexerSchema.subregistries,
domain: ensIndexerSchema.subgraph_domain,
})
.from(ensIndexerSchema.registrarActions)
// join Registration Lifecycles associated with Registrar Actions
.innerJoin(
ensIndexerSchema.registrationLifecycles,
eq(ensIndexerSchema.registrarActions.node, ensIndexerSchema.registrationLifecycles.node),
)
// join Domains associated with Registration Lifecycles
.innerJoin(
ensIndexerSchema.subgraph_domain,
eq(ensIndexerSchema.registrationLifecycles.node, ensIndexerSchema.subgraph_domain.id),
)
// join Subregistries associated with Registration Lifecycles
.innerJoin(
ensIndexerSchema.subregistries,
eq(
ensIndexerSchema.registrationLifecycles.subregistryId,
ensIndexerSchema.subregistries.subregistryId,
),
)
.where(and(...buildWhereClause(options.filters)))
.orderBy(buildOrderByClause(options.orderBy))
.limit(options.limit)
.offset(options.offset);

const records = await query;

return records;
return withSpanAsync(
tracer,
"registrarActions.find",
{
filterCount: options.filters?.length ?? 0,
orderBy: options.orderBy,
limit: options.limit,
offset: options.offset,
},
() =>
ensDb
.select({
registrarActions: ensIndexerSchema.registrarActions,
registrationLifecycles: ensIndexerSchema.registrationLifecycles,
subregistries: ensIndexerSchema.subregistries,
domain: ensIndexerSchema.subgraph_domain,
})
.from(ensIndexerSchema.registrarActions)
// join Registration Lifecycles associated with Registrar Actions
.innerJoin(
ensIndexerSchema.registrationLifecycles,
eq(ensIndexerSchema.registrarActions.node, ensIndexerSchema.registrationLifecycles.node),
)
// join Domains associated with Registration Lifecycles
.innerJoin(
ensIndexerSchema.subgraph_domain,
eq(ensIndexerSchema.registrationLifecycles.node, ensIndexerSchema.subgraph_domain.id),
)
// join Subregistries associated with Registration Lifecycles
.innerJoin(
ensIndexerSchema.subregistries,
eq(
ensIndexerSchema.registrationLifecycles.subregistryId,
ensIndexerSchema.subregistries.subregistryId,
),
)
.where(and(...buildWhereClause(options.filters)))
.orderBy(buildOrderByClause(options.orderBy))
.limit(options.limit)
.offset(options.offset),
);
}

type MapToNamedRegistrarActionArgs = Awaited<ReturnType<typeof _findRegistrarActions>>[0];
Expand All @@ -176,7 +184,7 @@ type MapToNamedRegistrarActionArgs = Awaited<ReturnType<typeof _findRegistrarAct
*/
function _mapToNamedRegistrarAction(record: MapToNamedRegistrarActionArgs): NamedRegistrarAction {
// Invariant: The FQDN `name` of the Domain associated with the `node` must exist.
if (!record.domain.name === null) {
if (record.domain.name === null) {
throw new Error(`Domain 'name' must exists for '${record.registrationLifecycles.node}' node.`);
}

Expand Down
Loading