feat(campaigns): API client for approval workflow
This commit is contained in:
@@ -34,6 +34,9 @@ export interface Campaign {
|
||||
start_date: string | null;
|
||||
scheduled_at: string | null;
|
||||
completed_at: string | null;
|
||||
approved_by: string | null;
|
||||
approved_at: string | null;
|
||||
rejection_reason: string | null;
|
||||
target_platform: string | null;
|
||||
tags: string[];
|
||||
created_at: string | null;
|
||||
@@ -65,7 +68,6 @@ export interface CampaignSummary {
|
||||
export interface CampaignCreatePayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
start_date?: string; // ISO date YYYY-MM-DD — campaign won't activate before this
|
||||
type?: string;
|
||||
threat_actor_id?: string;
|
||||
target_platform?: string;
|
||||
@@ -80,6 +82,35 @@ export interface AddTestPayload {
|
||||
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 ───────────────────────────────────────────────────
|
||||
|
||||
/** List campaigns with optional filters. */
|
||||
@@ -247,3 +278,93 @@ export async function getCampaignHistory(campaignId: string): Promise<{
|
||||
const { data } = await client.get(`/campaigns/${campaignId}/history`);
|
||||
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