mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-27 13:54:18 +00:00
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
215 lines
5.8 KiB
TypeScript
215 lines
5.8 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
|
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
|
import { scheduleUpdatePayloadSchema } from "@/lib/schedules";
|
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
|
import type {
|
|
ScheduleProps,
|
|
SchedulesBulkResponse,
|
|
ScheduleUpdatePayload,
|
|
} from "@/types/schedules";
|
|
|
|
// SSRF guard: the id is interpolated into the request URL, so only UUIDs pass.
|
|
const providerIdSchema = z.uuid();
|
|
|
|
function parseProviderId(providerId: string): string | null {
|
|
const parsed = providerIdSchema.safeParse(providerId);
|
|
return parsed.success ? parsed.data : null;
|
|
}
|
|
|
|
function parseProviderIds(providerIds: string[]): string[] | null {
|
|
if (providerIds.length === 0) return null;
|
|
|
|
const ids = providerIds.map(parseProviderId);
|
|
return ids.every((id): id is string => id !== null) ? ids : null;
|
|
}
|
|
|
|
function revalidateScheduleViews() {
|
|
revalidatePath("/scans");
|
|
revalidatePath("/providers");
|
|
}
|
|
|
|
export const getSchedule = async (providerId: string) => {
|
|
const id = parseProviderId(providerId);
|
|
if (!id) return { error: "Invalid provider id." };
|
|
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
const url = new URL(`${apiBaseUrl}/schedules/${id}`);
|
|
url.searchParams.set("include", "provider");
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), { headers });
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Lists every schedule (one per provider), following pagination — the backend
|
|
* has no multi-provider filter.
|
|
*/
|
|
export const getSchedules = async () => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
const schedules: ScheduleProps[] = [];
|
|
const MAX_PAGES = 20;
|
|
|
|
try {
|
|
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
const url = new URL(`${apiBaseUrl}/schedules`);
|
|
url.searchParams.set("page[number]", String(page));
|
|
url.searchParams.set("page[size]", "100");
|
|
|
|
const response = await fetch(url.toString(), { headers });
|
|
|
|
const result = await handleApiResponse(response);
|
|
if (result?.error) return result;
|
|
|
|
schedules.push(...(result?.data ?? []));
|
|
|
|
const totalPages = result?.meta?.pagination?.pages ?? 1;
|
|
if (page >= totalPages) break;
|
|
}
|
|
|
|
return { data: schedules };
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
};
|
|
|
|
/** Fetches one page of configured schedules for the Scheduled tab, with native pagination. */
|
|
export const getSchedulesPage = async ({
|
|
page = 1,
|
|
pageSize = 10,
|
|
sort = "",
|
|
filters = {},
|
|
}: {
|
|
page?: number;
|
|
pageSize?: number;
|
|
sort?: string;
|
|
filters?: Record<string, string | string[]>;
|
|
} = {}) => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
const url = new URL(`${apiBaseUrl}/schedules`);
|
|
|
|
url.searchParams.set("filter[configured]", "true");
|
|
url.searchParams.set("include", "provider");
|
|
url.searchParams.set("page[number]", String(page));
|
|
url.searchParams.set("page[size]", String(pageSize));
|
|
if (sort) url.searchParams.set("sort", sort);
|
|
|
|
for (const [key, value] of Object.entries(filters)) {
|
|
const normalized = Array.isArray(value) ? value.join(",") : value;
|
|
if (normalized) url.searchParams.set(key, normalized);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), { headers });
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
};
|
|
|
|
export const updateSchedule = async (
|
|
providerId: string,
|
|
payload: ScheduleUpdatePayload,
|
|
) => {
|
|
const id = parseProviderId(providerId);
|
|
if (!id) return { error: "Invalid provider id." };
|
|
|
|
const headers = await getAuthHeaders({ contentType: true });
|
|
const url = new URL(`${apiBaseUrl}/schedules/${id}`);
|
|
|
|
const body = {
|
|
data: {
|
|
type: "schedules",
|
|
id,
|
|
attributes: payload,
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
method: "PATCH",
|
|
headers,
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const result = await handleApiResponse(response);
|
|
if (!result?.error) {
|
|
revalidateScheduleViews();
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
};
|
|
|
|
export const updateSchedulesBulk = async (
|
|
providerIds: string[],
|
|
payload: ScheduleUpdatePayload,
|
|
): Promise<SchedulesBulkResponse> => {
|
|
const ids = parseProviderIds(providerIds);
|
|
if (!ids) return { error: "Invalid provider ids." };
|
|
|
|
const parsedPayload = scheduleUpdatePayloadSchema.safeParse(payload);
|
|
if (!parsedPayload.success) return { error: "Invalid schedule payload." };
|
|
|
|
try {
|
|
const headers = await getAuthHeaders({ contentType: true });
|
|
const url = new URL(`${apiBaseUrl}/schedules/bulk`);
|
|
const body = {
|
|
data: {
|
|
type: "schedules-bulk",
|
|
attributes: {
|
|
schedule: parsedPayload.data,
|
|
provider_ids: ids,
|
|
},
|
|
},
|
|
};
|
|
|
|
const response = await fetch(url.toString(), {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const result = (await handleApiResponse(response)) as SchedulesBulkResponse;
|
|
if (!result?.error) {
|
|
revalidateScheduleViews();
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
};
|
|
|
|
export const removeSchedule = async (providerId: string) => {
|
|
const id = parseProviderId(providerId);
|
|
if (!id) return { error: "Invalid provider id." };
|
|
|
|
const headers = await getAuthHeaders({ contentType: true });
|
|
const url = new URL(`${apiBaseUrl}/schedules/${id}`);
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
method: "DELETE",
|
|
headers,
|
|
});
|
|
|
|
const result = await handleApiResponse(response);
|
|
if (!result?.error) {
|
|
revalidateScheduleViews();
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
};
|