Files

326 lines
8.4 KiB
TypeScript

"use server";
import { redirect } from "next/navigation";
import { getLatestFindings } from "@/actions/findings";
import { listOrganizationsSafe } from "@/actions/organizations/organizations";
import {
apiBaseUrl,
FINDINGS_FILTERED_SORT,
GENERIC_SERVER_ERROR_MESSAGE,
getAuthHeaders,
sanitizeErrorMessage,
} from "@/lib";
import { appendSanitizedProviderTypeFilters } from "@/lib/provider-filters";
import { handleApiResponse } from "@/lib/server-actions-helper";
import { OrganizationResource } from "@/types/organizations";
export const getResources = async ({
page = 1,
query = "",
sort = "",
filters = {},
pageSize = 10,
include = "",
fields = [],
}: {
page?: number;
query?: string;
sort?: string;
filters?: Record<string, string | string[] | undefined>;
pageSize?: number;
include?: string;
fields?: string[];
}) => {
const headers = await getAuthHeaders({ contentType: false });
if (isNaN(Number(page)) || page < 1) redirect("resources");
const url = new URL(`${apiBaseUrl}/resources`);
if (fields.length > 0) {
url.searchParams.append("fields[resources]", fields.join(","));
}
if (page) url.searchParams.append("page[number]", page.toString());
if (pageSize) url.searchParams.append("page[size]", pageSize.toString());
if (include) url.searchParams.append("include", include);
if (query) url.searchParams.append("filter[search]", query);
if (sort) url.searchParams.append("sort", sort);
appendSanitizedProviderTypeFilters(url, filters);
try {
const response = await fetch(url.toString(), {
headers,
});
return handleApiResponse(response);
} catch (error) {
console.error("Error fetching resources:", error);
return undefined;
}
};
export const getLatestResources = async ({
page = 1,
query = "",
sort = "",
include = "",
filters = {},
pageSize = 10,
fields = [],
}: {
page?: number;
query?: string;
sort?: string;
filters?: Record<string, string | string[] | undefined>;
pageSize?: number;
include?: string;
fields?: string[];
}) => {
const headers = await getAuthHeaders({ contentType: false });
if (isNaN(Number(page)) || page < 1) redirect("resources");
const url = new URL(`${apiBaseUrl}/resources/latest`);
if (fields.length > 0) {
url.searchParams.append("fields[resources]", fields.join(","));
}
if (page) url.searchParams.append("page[number]", page.toString());
if (pageSize) url.searchParams.append("page[size]", pageSize.toString());
if (include) url.searchParams.append("include", include);
if (query) url.searchParams.append("filter[search]", query);
if (sort) url.searchParams.append("sort", sort);
appendSanitizedProviderTypeFilters(url, filters);
try {
const response = await fetch(url.toString(), {
headers,
});
return handleApiResponse(response);
} catch (error) {
console.error("Error fetching latest resources:", error);
return undefined;
}
};
export const getMetadataInfo = async ({
query = "",
sort = "",
filters = {},
}: {
query?: string;
sort?: string;
filters?: Record<string, string | string[] | undefined>;
}) => {
const headers = await getAuthHeaders({ contentType: false });
const url = new URL(`${apiBaseUrl}/resources/metadata`);
if (query) url.searchParams.append("filter[search]", query);
if (sort) url.searchParams.append("sort", sort);
appendSanitizedProviderTypeFilters(url, filters);
try {
const response = await fetch(url.toString(), {
headers,
});
return handleApiResponse(response);
} catch (error) {
console.error("Error fetching metadata info:", error);
return undefined;
}
};
export const getLatestMetadataInfo = async ({
query = "",
sort = "",
filters = {},
}: {
query?: string;
sort?: string;
filters?: Record<string, string | string[] | undefined>;
}) => {
const headers = await getAuthHeaders({ contentType: false });
const url = new URL(`${apiBaseUrl}/resources/metadata/latest`);
if (query) url.searchParams.append("filter[search]", query);
if (sort) url.searchParams.append("sort", sort);
appendSanitizedProviderTypeFilters(url, filters);
try {
const response = await fetch(url.toString(), {
headers,
});
return handleApiResponse(response);
} catch (error) {
console.error("Error fetching latest metadata info:", error);
return undefined;
}
};
const SAFE_RESOURCE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
export const getResourceEvents = async (
resourceId: string,
{
includeReadEvents = false,
lookbackDays = 90,
pageSize = 50,
}: {
includeReadEvents?: boolean;
lookbackDays?: number;
pageSize?: number;
} = {},
) => {
if (!SAFE_RESOURCE_ID_PATTERN.test(resourceId)) {
return { error: "Invalid resource ID format." };
}
const headers = await getAuthHeaders({ contentType: false });
const url = new URL(`${apiBaseUrl}/resources/${resourceId}/events`);
url.searchParams.append("include_read_events", String(includeReadEvents));
url.searchParams.append("lookback_days", String(lookbackDays));
url.searchParams.append("page[size]", String(pageSize));
try {
const response = await fetch(url.toString(), { headers });
if (!response.ok) {
const rawText = await response.text().catch(() => "");
const contentType =
response.headers.get("content-type")?.toLowerCase() || "";
const defaultError = "An error occurred while fetching events.";
const fallbackError = contentType.includes("text/html")
? GENERIC_SERVER_ERROR_MESSAGE
: response.statusText || defaultError;
try {
const errorData = rawText ? JSON.parse(rawText) : null;
const errorMessage =
errorData?.errors?.[0]?.detail ||
errorData?.error ||
errorData?.message ||
rawText ||
fallbackError;
return {
error: sanitizeErrorMessage(String(errorMessage), fallbackError),
status: response.status,
};
} catch {
return {
error: sanitizeErrorMessage(rawText || fallbackError, fallbackError),
status: response.status,
};
}
}
return handleApiResponse(response);
} catch (error) {
console.error("Error fetching resource events:", error);
return { error: "An unexpected error occurred." };
}
};
export const getResourceById = async (
id: string,
{
fields = [],
include = [],
}: {
fields?: string[];
include?: string[];
} = {},
) => {
const headers = await getAuthHeaders({ contentType: false });
const url = new URL(`${apiBaseUrl}/resources/${id}`);
if (fields.length > 0) {
url.searchParams.append("fields[resources]", fields.join(","));
}
if (include.length > 0) {
url.searchParams.append("include", include.join(","));
}
try {
const resource = await fetch(url.toString(), {
headers,
});
if (!resource.ok) {
throw new Error(`Error fetching resource: ${resource.status}`);
}
return handleApiResponse(resource);
} catch (error) {
console.error("Error fetching resource by ID:", error);
return undefined;
}
};
export const getResourceDrawerData = async ({
resourceId,
resourceUid,
providerId,
providerType,
page = 1,
pageSize = 10,
query = "",
}: {
resourceId: string;
resourceUid: string;
providerId: string;
providerType: string;
page?: number;
pageSize?: number;
query?: string;
}) => {
const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
const [resourceData, findingsResponse, organizationsResponse] =
await Promise.all([
getResourceById(resourceId, { fields: ["tags"] }),
getLatestFindings({
page,
pageSize,
query,
sort: FINDINGS_FILTERED_SORT,
filters: {
"filter[resource_uid]": resourceUid,
"filter[status]": "FAIL",
},
}),
isCloudEnv && providerType === "aws"
? listOrganizationsSafe()
: Promise.resolve({ data: [] }),
]);
const providerOrg =
providerType === "aws"
? (organizationsResponse.data.find((organization: OrganizationResource) =>
organization.relationships?.providers?.data?.some(
(provider: { id: string }) => provider.id === providerId,
),
) ?? null)
: null;
return {
findings: findingsResponse?.data ?? [],
findingsMeta: findingsResponse?.meta ?? null,
providerOrg,
resourceTags: resourceData?.data?.attributes.tags ?? {},
};
};