export type TTLOption = '1h' | '24h' | '7d' | 'never' export interface Item { slug: string kind: string title: string body?: string | null file_name?: string | null mime?: string | null size_bytes?: number | null is_public: boolean burn_after_read: boolean expires_at: string | null created_at: string view_count: number id?: number created_ip?: string burned?: boolean } export interface WallResponse { items: Item[] page: number page_size: number total: number } export interface Ban { ip: string reason: string created_at: string } async function parseError(res: Response): Promise { try { const data = await res.json() if (typeof data?.detail === 'string') return data.detail return JSON.stringify(data?.detail ?? data) } catch { return res.statusText || `HTTP ${res.status}` } } async function request(path: string, init?: RequestInit): Promise { const res = await fetch(path, init) if (!res.ok) { throw new Error(await parseError(res)) } if (res.status === 204) { return undefined as T } return (await res.json()) as T } export function createItem(payload: { body: string title?: string is_public: boolean burn_after_read: boolean ttl: TTLOption }): Promise { return request('/api/items', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export function uploadItem(form: FormData): Promise { return request('/api/items/upload', { method: 'POST', body: form, }) } export function fetchWall(page = 1, pageSize = 20): Promise { const q = new URLSearchParams({ page: String(page), page_size: String(pageSize), }) return request(`/api/items/wall?${q}`) } export function fetchItem(slug: string): Promise { return request(`/api/items/${encodeURIComponent(slug)}`) } export function itemFileUrl(slug: string): string { return `/api/items/${encodeURIComponent(slug)}/file` } export function adminLogin(password: string): Promise<{ token: string }> { return request<{ token: string }>('/api/admin/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }), }) } function authHeaders(token: string): HeadersInit { return { Authorization: `Bearer ${token}` } } export function adminListItems(token: string): Promise<{ items: Item[] }> { return request<{ items: Item[] }>('/api/admin/items', { headers: authHeaders(token), }) } export function adminDeleteItem(token: string, itemId: number): Promise<{ ok: boolean }> { return request<{ ok: boolean }>(`/api/admin/items/${itemId}`, { method: 'DELETE', headers: authHeaders(token), }) } export function adminListBans(token: string): Promise<{ bans: Ban[] }> { return request<{ bans: Ban[] }>('/api/admin/bans', { headers: authHeaders(token), }) } export function adminCreateBan( token: string, payload: { ip: string; reason?: string }, ): Promise<{ ok: boolean }> { return request<{ ok: boolean }>('/api/admin/bans', { method: 'POST', headers: { ...authHeaders(token), 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }) } export function adminDeleteBan(token: string, ip: string): Promise<{ ok: boolean }> { return request<{ ok: boolean }>(`/api/admin/bans/${encodeURIComponent(ip)}`, { method: 'DELETE', headers: authHeaders(token), }) }