// REST client for the imap-copier control API. // All requests carry the session cookie; a 401 anywhere bounces to #/login. export type TLSMode = 'ssl' | 'starttls' | 'plain' export interface Endpoint { id: number role_label: string host: string port: number tls_mode: TLSMode } export interface Task { id: number name: string src_endpoint_id: number dst_endpoint_id: number status: string folder_mapping?: Record } export type TestStatus = 'pending' | 'ok' | 'fail' | string export interface Account { id: number src_login: string dst_login: string test_src_status: TestStatus test_dst_status: TestStatus status: string copied: number skipped: number errors: number } export interface TaskDetail { task: Task accounts: Account[] } export class ApiError extends Error {} export async function api(path: string, opts: RequestInit = {}): Promise { const res = await fetch(path, { credentials: 'include', ...opts }) if (res.status === 401) { location.hash = '#/login' throw new ApiError('unauthorized') } if (!res.ok) { const body = await res.text() throw new ApiError(body || res.statusText) } const ct = res.headers.get('content-type') || '' if (ct.includes('application/json')) return res.json() as Promise return res.text() as unknown as T } const jsonBody = (body: unknown): RequestInit => ({ method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }) export const login = (user: string, pass: string) => api('/api/login', jsonBody({ user, pass })) export const logout = () => api('/api/logout', { method: 'POST' }) export const listEndpoints = () => api('/api/endpoints') export const createEndpoint = (body: { role_label: string; host: string; port: number; tls_mode: TLSMode }) => api<{ id: number }>('/api/endpoints', jsonBody(body)) export const updateEndpoint = ( id: number, body: { role_label: string; host: string; port: number; tls_mode: TLSMode }, ) => api(`/api/endpoints/${id}`, { ...jsonBody(body), method: 'PUT' }) export const deleteTask = (id: number) => api(`/api/tasks/${id}`, { method: 'DELETE' }) export const deleteAccount = (taskId: number, accountId: number) => api(`/api/tasks/${taskId}/accounts/${accountId}`, { method: 'DELETE' }) export const cancelAccount = (taskId: number, accountId: number) => api(`/api/tasks/${taskId}/accounts/${accountId}/cancel`, { method: 'POST' }) export const listTasks = () => api('/api/tasks') export const getTask = (id: number) => api(`/api/tasks/${id}`) export const createTask = (body: { name: string src_endpoint_id: number dst_endpoint_id: number folder_mapping?: Record }) => api<{ id: number }>('/api/tasks', jsonBody(body)) export const createAccount = ( id: number, body: { src_login: string; src_pass: string; dst_login: string; dst_pass: string }, ) => api<{ id: number }>(`/api/tasks/${id}/accounts`, jsonBody(body)) export const testAccounts = (id: number) => api(`/api/tasks/${id}/test`, { method: 'POST' }) export const runTask = (id: number) => api(`/api/tasks/${id}/run`, { method: 'POST' }) export const importCSV = (id: number, file: File) => { const fd = new FormData() fd.append('file', file) return api<{ imported: number }>(`/api/tasks/${id}/import`, { method: 'POST', body: fd }) }