feat(campaigns): API client for approval workflow
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user