import type { ApiSettings, CampaignListItem, DeltaDeletedItem } from "../types"; import { apiDownload, apiFetch } from "./client"; import { campaignJobsQueryParams, type CampaignJobsQueryParameters } from "../features/campaigns/utils/jobListQuery"; export { fetchResourceAccessExplanation } from "@govoplan/core-webui"; export type { AccessDecisionProvenanceItem, ResourceAccessExplanationUser as AccessExplanationUser, ResourceAccessExplanationResponse } from "@govoplan/core-webui"; 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; mail_profile_migration_required?: boolean; }; export type CampaignWorkspaceResponse = { campaign: CampaignListItem | null; versions: CampaignVersionListItem[]; current_version: CampaignVersionDetail | null; summary: CampaignSummary | null; selected_version_id?: string | null; }; export type CampaignDeltaResponse = { campaigns: CampaignListItem[]; deleted: DeltaDeletedItem[]; watermark?: string | null; has_more: boolean; full: boolean; }; export type CampaignWorkspaceDeltaResponse = CampaignWorkspaceResponse & { deleted: DeltaDeletedItem[]; watermark?: string | null; has_more: boolean; full: boolean; }; export type CampaignWorkspaceQuery = { versionId?: string | null; includeCurrentVersion?: boolean; includeSummary?: boolean; includeVersions?: boolean; since?: string | null; limit?: number; }; export type RecipientImportColumnKind = "ignore" | "id" | "active" | "name" | "from" | "to" | "cc" | "bcc" | "reply_to" | "field" | "new_field" | "attachment_pattern"; export type RecipientImportColumnMappingProfileItem = { columnIndex: number; kind: RecipientImportColumnKind; fieldName?: string | null; newFieldName?: string | null; }; export type RecipientImportMappingProfile = { id: string; name: string; createdAt: string; updatedAt: string; columnCount: number; headers: string[]; normalizedHeaders: string[]; orderedHeaderFingerprint: string; unorderedHeaderFingerprint: string; delimiter: "," | ";" | "\t"; headerRows: number; quoted: boolean; valueSeparators: string; mappings: RecipientImportColumnMappingProfileItem[]; }; export type RecipientImportMappingProfilePayload = Omit; export type RecipientImportMappingProfileListResponse = { profiles: RecipientImportMappingProfile[]; }; export type CampaignAddressLookupCandidate = { contact_id: string; address_book_id: string; display_name: string; email?: string | null; email_label?: string | null; organization?: string | null; role_title?: string | null; tags: string[]; source_kind: string; source_ref?: string | null; source_revision?: string | null; provenance: Record; }; export type CampaignAddressLookupResponse = { available: boolean; candidates: CampaignAddressLookupCandidate[]; }; export type CampaignRecipientAddressSource = { source_id: string; source_label: string; source_kind: string; source_revision: string; recipient_count: number; provenance: Record; }; export type CampaignRecipientAddressSourcesResponse = { available: boolean; sources: CampaignRecipientAddressSource[]; }; export type CampaignRecipientSnapshotItem = { contact_id: string; display_name: string; email: string; email_label?: string | null; fields: Record; provenance: Record; }; export type CampaignRecipientAddressSourceSnapshot = { source_id: string; source_label: string; source_kind: string; source_revision: string; generated_at: string; recipients: CampaignRecipientSnapshotItem[]; provenance: 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; migrate_legacy_mail_settings?: boolean; }; 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 CampaignDeliveryOptions = { campaign_id: string; version_id: string; worker_queue_available: boolean; synchronous_send: { allowed?: boolean; reason?: string | null; message?: string | null; eligible_recipient_job_count?: number; policy?: { max_recipient_jobs?: number; source?: string; deployment_max_recipient_jobs?: number; tenant_max_recipient_jobs?: number | null; }; }; }; 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 CampaignAppendSentPayload = { dry_run?: boolean; enqueue_celery?: boolean; run_inline?: 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; linked_to_campaign?: boolean; share?: Record | 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; linked_match_count?: number; unlinked_match_count?: number; issues: Record[]; }; export type CampaignAttachmentPreviewResponse = { campaign_id: string; version_id: string; shared_file_count: number; candidate_file_count?: number; matched_file_count?: number; linked_file_count?: number; unlinked_file_count?: number; rules: CampaignAttachmentPreviewRule[]; linkable_files?: CampaignAttachmentPreviewFile[]; unused_shared_files: CampaignAttachmentPreviewFile[]; }; export type CampaignAttachmentPreviewPayload = { include_unmatched?: boolean; include_unlinked_candidates?: boolean; campaign_json?: Record; }; export type CampaignAttachmentLinkMatchesPayload = { campaign_json?: Record | null; dry_run?: boolean; }; export type CampaignAttachmentLinkMatchesResponse = { campaign_id: string; version_id: string; matched_file_count: number; already_linked_file_count: number; linked_file_count: number; dry_run?: boolean; linked_files: CampaignAttachmentPreviewFile[]; linkable_files: CampaignAttachmentPreviewFile[]; }; 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 CampaignSendJobPayload = { include_warnings?: boolean; dry_run?: boolean; use_rate_limit?: boolean; enqueue_imap_task?: boolean; }; export type CampaignJobsQuery = Omit; export type CampaignJobsResponse = { jobs: Record[]; page: number; page_size: number; total: number; total_unfiltered: number; pages: number; cursor?: string | null; next_cursor?: string | null; 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 CampaignJobsDeltaResponse = CampaignJobsResponse & { deleted: DeltaDeletedItem[]; watermark?: string | null; has_more: boolean; full: boolean; }; 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 listCampaignsDelta( settings: ApiSettings, options: {since?: string | null;limit?: number;} = {}) : Promise { const params = new URLSearchParams(); if (options.since) params.set("since", options.since); if (options.limit) params.set("limit", String(options.limit)); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/delta${suffix}`); } export async function listRecipientImportMappingProfiles(settings: ApiSettings): Promise { const response = await apiFetch(settings, "/api/v1/campaigns/recipient-import/mapping-profiles"); return response.profiles ?? []; } export async function createRecipientImportMappingProfile( settings: ApiSettings, payload: RecipientImportMappingProfilePayload) : Promise { return apiFetch(settings, "/api/v1/campaigns/recipient-import/mapping-profiles", { method: "POST", body: JSON.stringify(payload) }); } export async function updateRecipientImportMappingProfile( settings: ApiSettings, profileId: string, payload: RecipientImportMappingProfilePayload) : Promise { return apiFetch(settings, `/api/v1/campaigns/recipient-import/mapping-profiles/${encodeURIComponent(profileId)}`, { method: "PUT", body: JSON.stringify(payload) }); } export async function deleteRecipientImportMappingProfile(settings: ApiSettings, profileId: string): Promise { await apiFetch(settings, `/api/v1/campaigns/recipient-import/mapping-profiles/${encodeURIComponent(profileId)}`, { method: "DELETE" }); } export async function lookupCampaignAddresses( settings: ApiSettings, campaignId: string, query: string, limit = 25) : Promise { const params = new URLSearchParams({ query, limit: String(limit) }); return apiFetch(settings, `/api/v1/campaigns/${campaignId}/address-lookup?${params.toString()}`); } export async function listCampaignRecipientAddressSources( settings: ApiSettings, campaignId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-address-sources`); } export async function snapshotCampaignRecipientAddressSource( settings: ApiSettings, campaignId: string, sourceId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-address-sources/snapshot`, { method: "POST", body: JSON.stringify({ source_id: sourceId }) }); } 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 ?? "i18n:govoplan-campaign.new_campaign.1f0d021c", 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 getCampaignSchemaUi(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/schemas/campaign/ui"); } export async function getCampaignWorkspace( settings: ApiSettings, campaignId: string, options: CampaignWorkspaceQuery = {}) : Promise { const params = new URLSearchParams(); if (options.versionId) params.set("version_id", options.versionId); if (options.includeCurrentVersion !== undefined) params.set("include_current_version", String(options.includeCurrentVersion)); if (options.includeSummary !== undefined) params.set("include_summary", String(options.includeSummary)); if (options.includeVersions !== undefined) params.set("include_versions", String(options.includeVersions)); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/workspace${suffix}`); } export async function getCampaignWorkspaceDelta( settings: ApiSettings, campaignId: string, options: CampaignWorkspaceQuery = {}) : Promise { const params = new URLSearchParams(); if (options.versionId) params.set("version_id", options.versionId); if (options.includeCurrentVersion !== undefined) params.set("include_current_version", String(options.includeCurrentVersion)); if (options.includeSummary !== undefined) params.set("include_summary", String(options.includeSummary)); if (options.includeVersions !== undefined) params.set("include_versions", String(options.includeVersions)); if (options.since) params.set("since", options.since); if (options.limit) params.set("limit", String(options.limit)); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/workspace/delta${suffix}`); } 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, linkUnsharedMatches = false) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/versions/${versionId}/validate`, { method: "POST", body: JSON.stringify({ check_files: checkFiles, link_unshared_matches: linkUnsharedMatches }) }); } 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 function linkCampaignAttachmentMatches( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignAttachmentLinkMatchesPayload = {}) : Promise { return apiFetch( settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/attachments/link-matches`, { 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 = campaignJobsQueryParams(options); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`); } export async function getCampaignJobsDelta( settings: ApiSettings, campaignId: string, options: CampaignJobsQuery & {since?: string | null;limit?: number;} = {}) : Promise { const params = campaignJobsQueryParams(options); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs/delta${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 sendCampaignJob( settings: ApiSettings, campaignId: string, jobId: string, payload: CampaignSendJobPayload = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}/send`, { method: "POST", body: JSON.stringify(payload) }); } export async function resolveCampaignJobOutcome( settings: ApiSettings, campaignId: string, jobId: string, decision: "smtp_accepted" | "not_sent" | "imap_appended" | "imap_not_appended", 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 getCampaignDeliveryOptions( settings: ApiSettings, campaignId: string, versionId?: string | null) : Promise { const query = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/delivery-options${query}`); } 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, payload: CampaignAppendSentPayload = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/append-sent`, { method: "POST", body: JSON.stringify({ dry_run: payload.dry_run ?? false, enqueue_celery: payload.enqueue_celery ?? true, run_inline: payload.run_inline ?? false }) }); } 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" }); }