160 lines
5.5 KiB
TypeScript
160 lines
5.5 KiB
TypeScript
import type { ApiAdminConsumerInput, ApiAdminInput } from "@hivegrid/view-models";
|
|
import { runWithRlsContext } from "@hivegrid/db/rls-context.server";
|
|
import { db } from "~/utils/db.server";
|
|
|
|
export type ApiAdminDeps = {
|
|
readonly db?: typeof db;
|
|
readonly now?: () => Date;
|
|
readonly platformLeadVisibility: {
|
|
readonly actorAdminUserId: string;
|
|
readonly canReadAll: boolean;
|
|
} | null;
|
|
};
|
|
|
|
export async function getApiAdminSnapshot(deps: ApiAdminDeps): Promise<ApiAdminInput> {
|
|
const client = deps.db ?? db;
|
|
const now = deps.now?.() ?? new Date();
|
|
const platformLeadWhere = deps.platformLeadVisibility
|
|
? ({
|
|
archivedAt: null,
|
|
...(deps.platformLeadVisibility.canReadAll
|
|
? {}
|
|
: {
|
|
assignedToAdminUserId: deps.platformLeadVisibility.actorAdminUserId,
|
|
}),
|
|
} as const)
|
|
: null;
|
|
|
|
const [failedLogs, registrationsPending, leadsNew, leadsQualified, recentRegistrations, recentLeads, tenants] = await Promise.all([
|
|
client.apiKeyLog.count({ where: { status: { gte: 400 } } }),
|
|
client.registration.count({ where: { createdTenantId: null } }),
|
|
platformLeadWhere ? client.adminLead.count({ where: { ...platformLeadWhere, status: "new" } }) : Promise.resolve(0),
|
|
platformLeadWhere ? client.adminLead.count({ where: { ...platformLeadWhere, status: "qualified" } }) : Promise.resolve(0),
|
|
client.registration.findMany({
|
|
orderBy: { createdAt: "desc" },
|
|
take: 10,
|
|
select: { id: true, email: true, company: true, createdAt: true, createdTenantId: true },
|
|
}),
|
|
platformLeadWhere
|
|
? client.adminLead.findMany({
|
|
where: platformLeadWhere,
|
|
orderBy: [{ capturedAt: "desc" }, { id: "desc" }],
|
|
take: 10,
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
company: true,
|
|
status: true,
|
|
capturedAt: true,
|
|
source: { select: { displayName: true } },
|
|
},
|
|
})
|
|
: Promise.resolve([]),
|
|
client.tenant.findMany({
|
|
orderBy: { name: "asc" },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
},
|
|
}),
|
|
]);
|
|
|
|
const tenantKeyGroups = await mapWithConcurrency(tenants, 8, async (tenant) => ({
|
|
tenant,
|
|
keys: await runWithRlsContext({ tenantId: tenant.id }, () =>
|
|
client.apiKey.findMany({
|
|
orderBy: { createdAt: "desc" },
|
|
select: { id: true, active: true, expires: true, createdAt: true },
|
|
})
|
|
),
|
|
}));
|
|
const allKeys = tenantKeyGroups.flatMap(({ keys }) => keys);
|
|
const keysTotal = allKeys.length;
|
|
const keysActive = allKeys.filter((key) => key.active && (!key.expires || key.expires > now)).length;
|
|
const keysExpired = allKeys.filter((key) => key.expires && key.expires <= now).length;
|
|
const consumers = tenantKeyGroups.filter(({ keys }) => keys.length > 0).slice(0, 50);
|
|
|
|
return {
|
|
metrics: [
|
|
{
|
|
id: "keys",
|
|
value: keysTotal,
|
|
labelKey: "admin.apiAdmin.metrics.keys.label",
|
|
detailKey: "admin.apiAdmin.metrics.keys.detail",
|
|
tone: keysTotal > 0 ? "success" : "warning",
|
|
},
|
|
{
|
|
id: "activeKeys",
|
|
value: keysActive,
|
|
labelKey: "admin.apiAdmin.metrics.activeKeys.label",
|
|
detailKey: "admin.apiAdmin.metrics.activeKeys.detail",
|
|
tone: keysActive > 0 ? "success" : "neutral",
|
|
},
|
|
{
|
|
id: "expiredKeys",
|
|
value: keysExpired,
|
|
labelKey: "admin.apiAdmin.metrics.expiredKeys.label",
|
|
detailKey: "admin.apiAdmin.metrics.expiredKeys.detail",
|
|
tone: keysExpired > 0 ? "warning" : "success",
|
|
},
|
|
{
|
|
id: "failedLogs",
|
|
value: failedLogs,
|
|
labelKey: "admin.apiAdmin.metrics.failedLogs.label",
|
|
detailKey: "admin.apiAdmin.metrics.failedLogs.detail",
|
|
tone: failedLogs > 0 ? "danger" : "success",
|
|
},
|
|
{
|
|
id: "inbound",
|
|
value: registrationsPending + leadsNew,
|
|
labelKey: "admin.apiAdmin.metrics.inbound.label",
|
|
detailKey: "admin.apiAdmin.metrics.inbound.detail",
|
|
tone: registrationsPending + leadsNew > 0 ? "warning" : "success",
|
|
},
|
|
],
|
|
consumers: consumers.map<ApiAdminConsumerInput>(({ tenant, keys }) => ({
|
|
id: tenant.id,
|
|
name: tenant.name,
|
|
slug: tenant.slug,
|
|
keyCount: keys.length,
|
|
activeKeyCount: keys.filter((key) => key.active && (!key.expires || key.expires > now)).length,
|
|
lastKeyCreatedAt: keys[0]?.createdAt.toISOString() ?? null,
|
|
})),
|
|
inbound: {
|
|
registrationsPending,
|
|
leadsNew,
|
|
leadsQualified,
|
|
recentRegistrations: recentRegistrations.map((registration) => ({
|
|
id: registration.id,
|
|
email: registration.email,
|
|
company: registration.company,
|
|
createdAt: registration.createdAt.toISOString(),
|
|
createdTenantId: registration.createdTenantId,
|
|
})),
|
|
recentLeads: recentLeads.map((lead) => ({
|
|
id: lead.id,
|
|
email: lead.email,
|
|
company: lead.company,
|
|
source: lead.source.displayName,
|
|
status: lead.status,
|
|
createdAt: lead.capturedAt.toISOString(),
|
|
})),
|
|
},
|
|
};
|
|
}
|
|
|
|
async function mapWithConcurrency<T, U>(items: readonly T[], concurrency: number, operation: (item: T) => Promise<U>): Promise<U[]> {
|
|
const results = new Array<U>(items.length);
|
|
let cursor = 0;
|
|
await Promise.all(
|
|
Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
while (cursor < items.length) {
|
|
const index = cursor++;
|
|
results[index] = await operation(items[index]!);
|
|
}
|
|
})
|
|
);
|
|
return results;
|
|
}
|