106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
const BASE = '/api';
|
|
|
|
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
headers: { 'Content-Type': 'application/json', ...init?.headers },
|
|
...init,
|
|
});
|
|
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
export type Address = { name: string | null; email: string | null };
|
|
|
|
export type Folder = {
|
|
id: string;
|
|
imap_path: string;
|
|
display_name: string;
|
|
special_use: string | null;
|
|
unread_count: number;
|
|
total_count: number;
|
|
synced_at: string | null;
|
|
};
|
|
|
|
export type Thread = {
|
|
id: string;
|
|
subject: string;
|
|
participants: Address[];
|
|
message_count: number;
|
|
unread_count: number;
|
|
last_message_at: string;
|
|
snippet: string | null;
|
|
has_flagged: boolean;
|
|
has_attachment: boolean;
|
|
};
|
|
|
|
export type MessageSummary = {
|
|
id: string;
|
|
folder_id: string;
|
|
thread_id: string | null;
|
|
from_addr: Address;
|
|
to_addrs: Address[];
|
|
cc_addrs: Address[];
|
|
subject: string;
|
|
date_sent: string;
|
|
snippet: string;
|
|
flags: { seen: boolean; flagged: boolean; answered: boolean; draft: boolean };
|
|
attachments: Array<{ name: string; size: number; type: string }>;
|
|
};
|
|
|
|
export type MessageFull = MessageSummary & {
|
|
body_text: string | null;
|
|
body_html: string | null;
|
|
};
|
|
|
|
export type SyncRequest = {
|
|
account_id: string;
|
|
client_version: number;
|
|
mutations: Mutation[];
|
|
};
|
|
|
|
export type MutationTargetKind = 'thread' | 'message' | 'draft';
|
|
|
|
export type Mutation = {
|
|
id: string;
|
|
op: 'flag' | 'move' | 'delete' | 'send' | 'draft_save' | 'draft_delete';
|
|
target_kind: MutationTargetKind;
|
|
target_id: string;
|
|
payload: Record<string, unknown>;
|
|
};
|
|
|
|
export type SyncResponse = {
|
|
applied: string[];
|
|
rejected: Array<{ id: string; reason: string }>;
|
|
server_version: number;
|
|
changes: Array<{ type: 'message' | 'thread'; data: unknown }>;
|
|
};
|
|
|
|
export const api = {
|
|
accounts: {
|
|
list: () => apiFetch<{ accounts: Array<{ id: string; email: string }> }>('/accounts'),
|
|
},
|
|
folders: {
|
|
list: (accountId: string) =>
|
|
apiFetch<{ folders: Folder[] }>(`/accounts/${accountId}/folders`),
|
|
},
|
|
threads: {
|
|
list: (folderId: string, params?: { limit?: number; before?: string }) => {
|
|
const qs = new URLSearchParams();
|
|
if (params?.limit) qs.set('limit', String(params.limit));
|
|
if (params?.before) qs.set('before', params.before);
|
|
return apiFetch<{ threads: Thread[]; next_cursor: string | null }>(
|
|
`/folders/${folderId}/threads?${qs}`
|
|
);
|
|
},
|
|
},
|
|
messages: {
|
|
inThread: (threadId: string) =>
|
|
apiFetch<{ messages: MessageSummary[] }>(`/threads/${threadId}/messages`),
|
|
get: (id: string) => apiFetch<MessageFull>(`/messages/${id}`),
|
|
},
|
|
search: (accountId: string, q: string) =>
|
|
apiFetch<{ results: MessageSummary[] }>(`/search?account_id=${accountId}&q=${encodeURIComponent(q)}`),
|
|
sync: (body: SyncRequest) =>
|
|
apiFetch<SyncResponse>('/sync', { method: 'POST', body: JSON.stringify(body) }),
|
|
};
|