231 lines
7.9 KiB
TypeScript
231 lines
7.9 KiB
TypeScript
import { adminGetAllLegacyTenantSummaries } from "@hivegrid/db/accounts.server";
|
|
import {
|
|
countLegacyApiKeyLogs,
|
|
deleteTenantLegacyApiKeyLogsByIds,
|
|
findLegacyApiKeyLogTenantScopesByIds,
|
|
getLegacyApiKeyLogAdminDetails,
|
|
groupLegacyApiKeyLogs,
|
|
tenantLegacyApiKeyLogScope,
|
|
type LegacyApiKeyLogOrderByInput,
|
|
type LegacyApiKeyLogWhereInput,
|
|
} from "@hivegrid/db/api-keys.server";
|
|
import { getRlsExecutionContext, runWithRlsContext } from "@hivegrid/db/rls-context.server";
|
|
import { Prisma } from "@hivegrid/db/prisma-browser";
|
|
import { Params } from "react-router";
|
|
import { FilterablePropertyDto } from "~/application/dtos/data/FilterablePropertyDto";
|
|
import { getFiltersFromCurrentUrl, getPaginationFromCurrentUrl } from "~/utils/helpers/RowPaginationHelper";
|
|
import { getTenantIdOrNull } from "~/utils/services/.server/urlService";
|
|
import { ApiCallSummaryDto } from "../dtos/ApiCallSummaryDto";
|
|
import ApiKeyLogUtils from "../utils/ApiKeyLogUtils";
|
|
import { ApiKeyLogDto } from "../dtos/ApiKeyLogDto";
|
|
import { PaginationDto } from "~/application/dtos/data/PaginationDto";
|
|
|
|
async function getSummary({ request, params }: { request: Request; params: Params }) {
|
|
const { filterableProperties, whereFilters, allTenants } = await getFilters({ request, params });
|
|
const searchParams = new URL(request.url).searchParams;
|
|
let groupBy = ApiKeyLogUtils.getGroupByValues(searchParams);
|
|
let items: ApiCallSummaryDto[] = [];
|
|
if (groupBy.length > 0) {
|
|
try {
|
|
const data = await groupLegacyApiKeyLogs({
|
|
by: groupBy,
|
|
where: whereFilters,
|
|
});
|
|
items = data.map((x) => x as ApiCallSummaryDto);
|
|
} catch {
|
|
throw Error("Invalid group by: " + groupBy.join(", "));
|
|
}
|
|
}
|
|
return { items, allTenants, filterableProperties };
|
|
}
|
|
|
|
async function getDetails({ request, params }: { request: Request; params: Params }): Promise<{
|
|
items: ApiKeyLogDto[];
|
|
filterableProperties: FilterablePropertyDto[];
|
|
pagination: PaginationDto;
|
|
}> {
|
|
const { filterableProperties, whereFilters } = await getFilters({ request, params });
|
|
|
|
const urlSearchParams = new URL(request.url).searchParams;
|
|
const pagination = getPaginationFromCurrentUrl(urlSearchParams);
|
|
|
|
const orderBy: LegacyApiKeyLogOrderByInput | LegacyApiKeyLogOrderByInput[] = !pagination.sortedBy.length
|
|
? { createdAt: "desc" }
|
|
: pagination.sortedBy.map((x) => ({
|
|
[x.name]: x.direction,
|
|
}));
|
|
const items = await getLegacyApiKeyLogAdminDetails({
|
|
take: pagination.pageSize,
|
|
skip: pagination.pageSize * (pagination.page - 1),
|
|
where: whereFilters,
|
|
includeTenant: !whereFilters.tenantId,
|
|
orderBy,
|
|
});
|
|
const totalItems = await countLegacyApiKeyLogs(whereFilters);
|
|
|
|
return {
|
|
items: items.map(toApiKeyLogDto),
|
|
filterableProperties,
|
|
pagination: {
|
|
page: pagination.page,
|
|
pageSize: pagination.pageSize,
|
|
totalItems,
|
|
totalPages: Math.ceil(totalItems / pagination.pageSize),
|
|
},
|
|
};
|
|
}
|
|
|
|
async function getGroupBys() {
|
|
const allMethods = [
|
|
{ name: "GET", value: "GET" },
|
|
{ name: "POST", value: "POST" },
|
|
{ name: "PUT", value: "PUT" },
|
|
{ name: "PATCH", value: "PATCH" },
|
|
{ name: "DELETE", value: "DELETE" },
|
|
];
|
|
|
|
return {
|
|
allMethods,
|
|
};
|
|
}
|
|
async function getFilterableProperties(tenantId: string | null) {
|
|
const { allMethods } = await getGroupBys();
|
|
const filterableProperties: FilterablePropertyDto[] = [
|
|
{ name: "method", title: "models.apiKeyLog.method", options: allMethods },
|
|
{
|
|
name: "endpoint",
|
|
title: "models.apiKeyLog.endpoint",
|
|
},
|
|
{
|
|
name: "params",
|
|
title: "models.apiKeyLog.params",
|
|
},
|
|
{
|
|
name: "status",
|
|
title: "models.apiKeyLog.status",
|
|
},
|
|
{
|
|
name: "apiKeyId",
|
|
title: "models.apiKey.object",
|
|
},
|
|
];
|
|
let allTenants: { id: string; name: string; slug: string }[] = [];
|
|
if (tenantId === null) {
|
|
allTenants = await adminGetAllLegacyTenantSummaries();
|
|
filterableProperties.unshift({
|
|
name: "tenantId",
|
|
title: "models.tenant.object",
|
|
options: [
|
|
{ value: "null", name: "{null}" },
|
|
...allTenants.map((item) => {
|
|
return {
|
|
value: item.id,
|
|
name: item.name,
|
|
};
|
|
}),
|
|
],
|
|
});
|
|
}
|
|
return { filterableProperties, allTenants };
|
|
}
|
|
|
|
async function getFilters({ request, params }: { request: Request; params: Params }) {
|
|
const tenantId = await getTenantIdOrNull({ request, params });
|
|
const { filterableProperties, allTenants } = await getFilterableProperties(tenantId);
|
|
const filters = getFiltersFromCurrentUrl(request, filterableProperties);
|
|
const q = filters.query || "";
|
|
|
|
const AND_filters: Prisma.ApiKeyLogWhereInput[] = [];
|
|
filterableProperties.forEach((filter) => {
|
|
const value = filters.properties.find((f) => f.name === filter.name)?.value;
|
|
if (value) {
|
|
let formattedValue = filter.name === "status" ? Number(value) : value;
|
|
AND_filters.push({
|
|
[filter.name]: value === "null" ? null : formattedValue,
|
|
});
|
|
}
|
|
});
|
|
|
|
const OR_filters: Prisma.ApiKeyLogWhereInput[] = [];
|
|
if (q) {
|
|
OR_filters.push(
|
|
{ ip: { contains: q, mode: "insensitive" } },
|
|
{ endpoint: { contains: q, mode: "insensitive" } },
|
|
{ method: { contains: q, mode: "insensitive" } },
|
|
{ params: { contains: q, mode: "insensitive" } },
|
|
{ error: { contains: q, mode: "insensitive" } }
|
|
);
|
|
}
|
|
const whereFilters: LegacyApiKeyLogWhereInput = {};
|
|
if (OR_filters.length > 0) {
|
|
whereFilters.OR = OR_filters;
|
|
}
|
|
if (AND_filters.length > 0) {
|
|
whereFilters.AND = AND_filters;
|
|
}
|
|
if (tenantId) {
|
|
const existingAndFilters = Array.isArray(whereFilters.AND) ? whereFilters.AND : whereFilters.AND ? [whereFilters.AND] : [];
|
|
whereFilters.AND = [...existingAndFilters, tenantLegacyApiKeyLogScope(tenantId)];
|
|
}
|
|
|
|
return { filterableProperties, whereFilters, allTenants };
|
|
}
|
|
|
|
async function adminDeleteMany(ids: string[]): Promise<{ deletedCount: number; rejectedTenantlessIds: string[] }> {
|
|
const context = getRlsExecutionContext();
|
|
if (!context?.userId || context.tenantId) {
|
|
throw new Error("Admin API log deletion requires an unscoped authenticated operator context");
|
|
}
|
|
|
|
const normalizedIds = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
if (normalizedIds.length === 0) return { deletedCount: 0, rejectedTenantlessIds: [] };
|
|
if (normalizedIds.length > 500) {
|
|
throw new Error("At most 500 API log entries can be deleted at once");
|
|
}
|
|
|
|
const scopes = await findLegacyApiKeyLogTenantScopesByIds(normalizedIds);
|
|
const rejectedTenantlessIds = scopes.filter((scope) => !scope.tenantId).map((scope) => scope.id);
|
|
if (rejectedTenantlessIds.length > 0) {
|
|
return { deletedCount: 0, rejectedTenantlessIds };
|
|
}
|
|
|
|
const byTenant = new Map<string, string[]>();
|
|
for (const scope of scopes) {
|
|
if (!scope.tenantId) continue;
|
|
const tenantIds = byTenant.get(scope.tenantId) ?? [];
|
|
tenantIds.push(scope.id);
|
|
byTenant.set(scope.tenantId, tenantIds);
|
|
}
|
|
await Promise.all(
|
|
[...byTenant].map(([tenantId, tenantIds]) => runWithRlsContext({ tenantId }, () => deleteTenantLegacyApiKeyLogsByIds({ ids: tenantIds, tenantId })))
|
|
);
|
|
return { deletedCount: scopes.length, rejectedTenantlessIds: [] };
|
|
}
|
|
|
|
async function deleteManyForTenant(ids: string[], tenantId: string | null) {
|
|
return tenantId
|
|
? runWithRlsContext({ tenantId }, () => deleteTenantLegacyApiKeyLogsByIds({ ids, tenantId }))
|
|
: deleteTenantLegacyApiKeyLogsByIds({ ids, tenantId });
|
|
}
|
|
|
|
function toApiKeyLogDto(item: Awaited<ReturnType<typeof getLegacyApiKeyLogAdminDetails>>[number]): ApiKeyLogDto {
|
|
if (item.apiKey || !item.apiKeyId || !item.alias || !item.tenantId) return item as ApiKeyLogDto;
|
|
return {
|
|
...item,
|
|
apiKey: {
|
|
alias: item.alias,
|
|
tenant: {
|
|
id: item.tenantId,
|
|
name: item.tenant?.name ?? item.tenantName ?? item.tenantId,
|
|
},
|
|
},
|
|
} as ApiKeyLogDto;
|
|
}
|
|
|
|
export default {
|
|
getSummary,
|
|
getDetails,
|
|
adminDeleteMany,
|
|
deleteManyForTenant,
|
|
};
|