feat(campaigns): API client for approval workflow

This commit is contained in:
kitos
2026-07-03 14:29:30 +02:00
parent 108743daa5
commit 15ef555131
+122 -1
View File
@@ -34,6 +34,9 @@ export interface Campaign {
start_date: string | null; start_date: string | null;
scheduled_at: string | null; scheduled_at: string | null;
completed_at: string | null; completed_at: string | null;
approved_by: string | null;
approved_at: string | null;
rejection_reason: string | null;
target_platform: string | null; target_platform: string | null;
tags: string[]; tags: string[];
created_at: string | null; created_at: string | null;
@@ -65,7 +68,6 @@ export interface CampaignSummary {
export interface CampaignCreatePayload { export interface CampaignCreatePayload {
name: string; name: string;
description?: string; description?: string;
start_date?: string; // ISO date YYYY-MM-DD — campaign won't activate before this
type?: string; type?: string;
threat_actor_id?: string; threat_actor_id?: string;
target_platform?: string; target_platform?: string;
@@ -80,6 +82,35 @@ export interface AddTestPayload {
phase?: string; phase?: string;
} }
export type ModificationRequestAction = "add_test" | "remove_test";
export type ModificationRequestStatus = "pending" | "approved" | "rejected";
export interface CampaignModificationRequest {
id: string;
campaign_id: string;
campaign_name: string | null;
requested_by: string | null;
action: ModificationRequestAction;
test_id: string | null;
test_name: string | null;
order_index: number | null;
phase: string | null;
justification: string;
status: ModificationRequestStatus;
reviewed_by: string | null;
reviewed_at: string | null;
review_notes: string | null;
created_at: string | null;
}
export interface CampaignTimelineEntry {
id: string;
action: string;
user_id: string | null;
timestamp: string;
details: Record<string, unknown>;
}
// ── API Functions ─────────────────────────────────────────────────── // ── API Functions ───────────────────────────────────────────────────
/** List campaigns with optional filters. */ /** List campaigns with optional filters. */
@@ -247,3 +278,93 @@ export async function getCampaignHistory(campaignId: string): Promise<{
const { data } = await client.get(`/campaigns/${campaignId}/history`); const { data } = await client.get(`/campaigns/${campaignId}/history`);
return data; return data;
} }
// ── Approval workflow ──────────────────────────────────────────────
/** Submit a draft campaign into the manager's approval queue. */
export async function submitCampaignForApproval(campaignId: string): Promise<Campaign> {
const { data } = await client.post<Campaign>(`/campaigns/${campaignId}/submit`);
return data;
}
/** Manager approves a pending campaign, fixing its start date. */
export async function approveCampaign(campaignId: string, startDate: string): Promise<Campaign> {
const { data } = await client.post<Campaign>(`/campaigns/${campaignId}/approve`, {
start_date: startDate,
});
return data;
}
/** Manager rejects a pending campaign with a mandatory reason. */
export async function rejectCampaign(campaignId: string, reason: string): Promise<Campaign> {
const { data } = await client.post<Campaign>(`/campaigns/${campaignId}/reject`, { reason });
return data;
}
// ── Modification requests ───────────────────────────────────────────
/** File a request to add/remove a test on an active campaign. */
export async function createModificationRequest(
campaignId: string,
payload: {
action: ModificationRequestAction;
test_id: string;
justification: string;
order_index?: number;
phase?: string;
},
): Promise<CampaignModificationRequest> {
const { data } = await client.post<CampaignModificationRequest>(
`/campaigns/${campaignId}/modification-requests`,
payload,
);
return data;
}
/** List modification requests filed against a specific campaign. */
export async function listCampaignModificationRequests(
campaignId: string,
): Promise<CampaignModificationRequest[]> {
const { data } = await client.get<CampaignModificationRequest[]>(
`/campaigns/${campaignId}/modification-requests`,
);
return data;
}
/** Manager's global queue of modification requests awaiting review. */
export async function listPendingModificationRequests(): Promise<CampaignModificationRequest[]> {
const { data } = await client.get<CampaignModificationRequest[]>(
"/campaigns/modification-requests/pending",
);
return data;
}
/** Manager approves a modification request — the test change is applied now. */
export async function approveModificationRequest(
requestId: string,
): Promise<CampaignModificationRequest> {
const { data } = await client.post<CampaignModificationRequest>(
`/campaigns/modification-requests/${requestId}/approve`,
);
return data;
}
/** Manager rejects a modification request with mandatory review notes. */
export async function rejectModificationRequest(
requestId: string,
reviewNotes: string,
): Promise<CampaignModificationRequest> {
const { data } = await client.post<CampaignModificationRequest>(
`/campaigns/modification-requests/${requestId}/reject`,
{ review_notes: reviewNotes },
);
return data;
}
// ── Timeline ─────────────────────────────────────────────────────────
/** Get the audit-log timeline for a campaign. */
export async function getCampaignTimeline(campaignId: string): Promise<CampaignTimelineEntry[]> {
const { data } = await client.get<CampaignTimelineEntry[]>(`/campaigns/${campaignId}/timeline`);
return data;
}