Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/modules/api/services/ApiKeyLogService.ts
T

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,
};