import type { ApiSettings, CampaignListItem } from "../types"; import { apiDownload, apiFetch } from "./client"; export type CampaignListResponse = | CampaignListItem[] | { campaigns?: CampaignListItem[]; items?: CampaignListItem[]; results?: CampaignListItem[]; }; export type CampaignShare = { id: string; campaign_id: string; target_type: "user" | "group"; target_id: string; permission: "read" | "write"; revoked_at?: string | null; }; export type CampaignShareTarget = { id: string; name: string; secondary?: string | null }; export type CampaignShareTargets = { users: CampaignShareTarget[]; groups: CampaignShareTarget[] }; export type CampaignUpdatePayload = { external_id?: string | null; name?: string | null; status?: string | null; description?: string | null; }; export type CampaignCreateMinimalPayload = { external_id?: string; name?: string; description?: string | null; current_flow?: string; current_step?: string; }; export type CampaignCreateResponse = { campaign: CampaignListItem & { current_version_id?: string | null; }; version: CampaignVersionListItem; }; export type CampaignVersionListItem = { id: string; campaign_id: string; version_number: number; schema_version?: string; source_filename?: string | null; source_base_path?: string | null; workflow_state?: string; current_flow?: string; current_step?: string | null; is_complete?: boolean; editor_state?: Record; autosaved_at?: string | null; published_at?: string | null; locked_at?: string | null; locked_by_user_id?: string | null; user_lock_state?: "temporary" | "permanent" | null; user_locked_at?: string | null; user_locked_by_user_id?: string | null; created_at?: string; updated_at?: string; validation_summary?: Record | null; build_summary?: Record | null; execution_snapshot_hash?: string | null; execution_snapshot_at?: string | null; }; export type CampaignVersionDetail = CampaignVersionListItem & { raw_json: Record; campaign_json?: Record; }; export type CampaignVersionUpdatePayload = { campaign_json?: Record | null; current_flow?: string | null; current_step?: string | null; workflow_state?: string | null; is_complete?: boolean | null; editor_state?: Record | null; source_filename?: string | null; source_base_path?: string | null; }; export type CampaignPartialValidationPayload = { campaign_json?: Record | null; section?: string | null; }; export type CampaignPartialValidationResponse = { ok: boolean; section?: string | null; error_count: number; warning_count: number; info_count: number; issues: Record[]; }; export type CampaignSummary = { generated_at?: string; selected_version_id?: string | null; campaign?: CampaignListItem; current_version?: { id: string; version_number?: number; schema_version?: string; source_filename?: string | null; created_at?: string | null; validation_summary?: Record | null; build_summary?: Record | null; } | null; cards?: { jobs_total?: number; inactive?: number; queueable?: number; needs_attention?: number; sent?: number; smtp_accepted?: number; failed?: number; outcome_unknown?: number; not_attempted?: number; queued_or_active?: number; cancelled?: number; partially_completed?: boolean; imap_appended?: number; imap_failed?: number; }; status_counts?: Record>; issues?: Record; attachments?: Record; attempts?: Record; delivery?: Record; recent_failures?: Record[]; }; export type CampaignQueuePayload = { version_id?: string | null; include_warnings?: boolean; enqueue_celery?: boolean; dry_run?: boolean; }; export type CampaignSendNowPayload = { version_id?: string | null; include_warnings?: boolean; check_files?: boolean; validate_before_send?: boolean; build_before_send?: boolean; dry_run?: boolean; use_rate_limit?: boolean; enqueue_imap_task?: boolean; }; export type CampaignAttachmentPreviewFile = { id: string; version_id?: string; blob_id?: string; display_path: string; filename: string; owner_type: string; owner_id: string; checksum_sha256?: string; size_bytes?: number; content_type?: string | null; }; export type CampaignAttachmentPreviewRule = { source: "global" | "entry"; entry_index: number; entry_id?: string | null; index: number; attachment_id?: string | null; label?: string | null; required: boolean; pattern: string; base_path_name?: string | null; base_path?: string | null; status: "ok" | "missing" | "ambiguous"; behavior?: string | null; zip_included?: boolean; zip_mode?: "inherit" | "include" | "exclude"; zip_archive_id?: string | null; zip_filename?: string | null; matches: CampaignAttachmentPreviewFile[]; match_count: number; issues: Record[]; }; export type CampaignAttachmentPreviewResponse = { campaign_id: string; version_id: string; shared_file_count: number; rules: CampaignAttachmentPreviewRule[]; unused_shared_files: CampaignAttachmentPreviewFile[]; }; export type CampaignAttachmentPreviewPayload = { include_unmatched?: boolean; campaign_json?: Record; }; export type CampaignMockSendPayload = { version_id?: string | null; send?: boolean; include_warnings?: boolean; include_needs_review?: boolean; append_sent?: boolean; clear_mailbox?: boolean; check_files?: boolean; }; export type CampaignReviewStatePayload = { inspection_complete: boolean; reviewed_message_keys: string[]; }; export type CampaignJobsQuery = { versionId?: string; page?: number; pageSize?: number; sendStatus?: string[]; validationStatus?: string[]; imapStatus?: string[]; query?: string; }; export type CampaignJobsResponse = { jobs: Record[]; page: number; page_size: number; total: number; total_unfiltered: number; pages: number; counts: Record>; filtered_counts: Record>; review: { inspection_complete?: boolean; blocking_count?: number; required_count?: number; reviewed_required_count?: number; bulk_acceptable_count?: number; }; }; export type CampaignJobDetailResponse = { job: Record; attempts: { smtp?: Record[]; imap?: Record[]; }; }; export type CampaignReportEmailPayload = { to: string[]; version_id?: string; include_jobs?: boolean; attach_jobs_csv?: boolean; attach_report_json?: boolean; dry_run?: boolean; }; export async function listCampaigns(settings: ApiSettings): Promise { const response = await apiFetch(settings, "/api/v1/campaigns"); if (Array.isArray(response)) { return response; } return response.campaigns ?? response.items ?? response.results ?? []; } export async function getCampaign(settings: ApiSettings, campaignId: string): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}`); } export async function updateCampaignMetadata( settings: ApiSettings, campaignId: string, payload: CampaignUpdatePayload ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}`, { method: "PUT", body: JSON.stringify(payload) }); } export async function createNewCampaign( settings: ApiSettings, overrides: CampaignCreateMinimalPayload = {} ): Promise { const now = new Date(); const stamp = now.toISOString().slice(0, 19).replace(/[-:T]/g, ""); const payload = { external_id: overrides.external_id ?? `new-campaign-${stamp}`, name: overrides.name ?? "New Campaign", description: overrides.description ?? "", current_flow: overrides.current_flow ?? "create", current_step: overrides.current_step ?? "basics" }; return apiFetch(settings, "/api/v1/campaigns/new", { method: "POST", body: JSON.stringify(payload) }); } export async function getCampaignSchema(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/schemas/campaign"); } export async function listCampaignVersions( settings: ApiSettings, campaignId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions`); } export async function getCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`); } export async function unlockCampaignVersionValidation( settings: ApiSettings, campaignId: string, versionId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/unlock-validation`, { method: "POST" }); } export async function lockCampaignVersionTemporarily( settings: ApiSettings, campaignId: string, versionId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/lock-temporarily`, { method: "POST" }); } export async function unlockCampaignVersionUserLock( settings: ApiSettings, campaignId: string, versionId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/unlock-user-lock`, { method: "POST" }); } export async function lockCampaignVersionPermanently( settings: ApiSettings, campaignId: string, versionId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/lock-permanently`, { method: "POST" }); } export async function updateCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignVersionUpdatePayload ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, { method: "PUT", body: JSON.stringify(payload) }); } export async function forkCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignVersionUpdatePayload = {} ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/fork`, { method: "POST", body: JSON.stringify(payload) }); } export async function autosaveCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignVersionUpdatePayload ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/autosave`, { method: "POST", body: JSON.stringify(payload) }); } export async function setCampaignVersionStep( settings: ApiSettings, campaignId: string, versionId: string, currentStep: string, currentFlow?: string | null ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/set-step`, { method: "POST", body: JSON.stringify({ current_flow: currentFlow, current_step: currentStep }) }); } export async function validatePartial( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignPartialValidationPayload = {} ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/validate-partial`, { method: "POST", body: JSON.stringify(payload) }); } export async function publishCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/publish`, { method: "POST" }); } export async function validateVersion( settings: ApiSettings, versionId: string, checkFiles = false ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/versions/${versionId}/validate`, { method: "POST", body: JSON.stringify({ check_files: checkFiles }) }); } export async function buildVersion( settings: ApiSettings, versionId: string, writeEml = true ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/versions/${versionId}/build`, { method: "POST", body: JSON.stringify({ write_eml: writeEml }) }); } export function previewCampaignAttachments( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignAttachmentPreviewPayload = {} ): Promise { return apiFetch( settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/attachments/preview`, { method: "POST", body: JSON.stringify(payload) } ); } export async function getCampaignSummary( settings: ApiSettings, campaignId: string, versionId?: string ): Promise { const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/summary${suffix}`); } export async function getCampaignJobs( settings: ApiSettings, campaignId: string, options: CampaignJobsQuery = {} ): Promise { const params = new URLSearchParams(); if (options.versionId) params.set("version_id", options.versionId); if (options.page) params.set("page", String(options.page)); if (options.pageSize) params.set("page_size", String(options.pageSize)); for (const value of options.sendStatus ?? []) params.append("send_status", value); for (const value of options.validationStatus ?? []) params.append("validation_status", value); for (const value of options.imapStatus ?? []) params.append("imap_status", value); if (options.query?.trim()) params.set("q", options.query.trim()); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`); } export async function getCampaignJobDetail( settings: ApiSettings, campaignId: string, jobId: string ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}`); } export async function getCampaignReport( settings: ApiSettings, campaignId: string, versionId?: string ): Promise { const params = new URLSearchParams({ include_jobs: "false" }); if (versionId) params.set("version_id", versionId); return apiFetch(settings, `/api/v1/campaigns/${campaignId}/report?${params.toString()}`); } export async function downloadCampaignJobsCsv( settings: ApiSettings, campaignId: string, versionId?: string ): Promise { const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; await apiDownload( settings, `/api/v1/campaigns/${campaignId}/report/jobs.csv${suffix}`, `campaign-${campaignId}${versionId ? `-${versionId}` : ""}-jobs.csv` ); } export async function emailCampaignReport( settings: ApiSettings, campaignId: string, payload: CampaignReportEmailPayload ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/report/email`, { method: "POST", body: JSON.stringify(payload) }); } export async function retryCampaignJobs( settings: ApiSettings, campaignId: string, payload: Record = {} ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/retry`, { method: "POST", body: JSON.stringify(payload) }); } export async function sendUnattemptedCampaignJobs( settings: ApiSettings, campaignId: string, payload: Record = {} ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/send-unattempted`, { method: "POST", body: JSON.stringify(payload) }); } export async function resolveCampaignJobOutcome( settings: ApiSettings, campaignId: string, jobId: string, decision: "smtp_accepted" | "not_sent", note?: string ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}/resolve-outcome`, { method: "POST", body: JSON.stringify({ decision, note: note || null }) }); } export async function updateCampaignReviewState( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignReviewStatePayload ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/review-state`, { method: "POST", body: JSON.stringify(payload) }); } export async function queueCampaign( settings: ApiSettings, campaignId: string, payload: CampaignQueuePayload = {} ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/queue`, { method: "POST", body: JSON.stringify(payload) }); } export async function sendCampaignNow( settings: ApiSettings, campaignId: string, payload: CampaignSendNowPayload = {} ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/send-now`, { method: "POST", body: JSON.stringify(payload) }); } export async function mockSendCampaign( settings: ApiSettings, campaignId: string, payload: CampaignMockSendPayload = {} ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/mock-send`, { method: "POST", body: JSON.stringify(payload) }); } export async function pauseCampaign(settings: ApiSettings, campaignId: string): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/pause`, { method: "POST" }); } export async function resumeCampaign(settings: ApiSettings, campaignId: string): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/resume`, { method: "POST" }); } export async function cancelCampaign(settings: ApiSettings, campaignId: string): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/cancel`, { method: "POST" }); } export async function appendSent( settings: ApiSettings, campaignId: string, dryRun = false ): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/append-sent`, { method: "POST", body: JSON.stringify({ dry_run: dryRun }) }); } export async function getCampaignShareTargets(settings: ApiSettings, campaignId: string): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/share-targets`); } export async function getCampaignShares(settings: ApiSettings, campaignId: string): Promise { const response = await apiFetch<{ shares: CampaignShare[] }>(settings, `/api/v1/campaigns/${campaignId}/shares`); return response.shares; } export async function updateCampaignOwner( settings: ApiSettings, campaignId: string, payload: { owner_user_id?: string | null; owner_group_id?: string | null } ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/owner`, { method: "PUT", body: JSON.stringify(payload) }); } export async function upsertCampaignShare( settings: ApiSettings, campaignId: string, payload: { target_type: "user" | "group"; target_id: string; permission: "read" | "write" } ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/shares`, { method: "POST", body: JSON.stringify(payload) }); } export async function revokeCampaignShare(settings: ApiSettings, campaignId: string, shareId: string): Promise { await apiFetch(settings, `/api/v1/campaigns/${campaignId}/shares/${shareId}`, { method: "DELETE" }); }