import { Category, Feed, FeedEntry, Stats, User } from '../types'; const API_BASE = '/api'; async function request(path: string, options: RequestInit = {}): Promise { const token = localStorage.getItem('rsspipe_jwt'); const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record || {}), }; if (token) { headers['Authorization'] = `Bearer ${token}`; } const res = await fetch(`${API_BASE}${path}`, { ...options, headers, credentials: 'omit', }); if (res.status === 401 && path !== '/auth/login') { localStorage.removeItem('rsspipe_jwt'); window.dispatchEvent(new CustomEvent('auth:unauthorized')); throw new Error('Session expired or unauthorized'); } if (!res.ok) { let errMsg = `Request failed: ${res.status} ${res.statusText}`; try { const errJson = await res.json(); if (errJson.error) errMsg = errJson.error; } catch { // ignore } throw new Error(errMsg); } return res.json(); } export const api = { // Auth async login(username: string, password: string): Promise<{ token: string; user: User }> { const res = await request<{ token: string; user: User }>('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); if (res.token) { localStorage.setItem('rsspipe_jwt', res.token); } return res; }, async logout(): Promise { try { await request('/auth/logout', { method: 'POST' }); } finally { localStorage.removeItem('rsspipe_jwt'); } }, async getMe(): Promise { return request('/auth/me'); }, async changePassword(old_password: string, new_password: string): Promise<{ message: string }> { return request<{ message: string }>('/auth/password', { method: 'PUT', body: JSON.stringify({ old_password, new_password }), }); }, // Stats async getStats(): Promise { return request('/stats'); }, // Categories async listCategories(): Promise { return request('/categories'); }, async createCategory(title: string): Promise { return request('/categories', { method: 'POST', body: JSON.stringify({ title }), }); }, async deleteCategory(id: string): Promise { return request(`/categories/${id}`, { method: 'DELETE' }); }, // Feeds async listFeeds(): Promise { return request('/feeds'); }, async createFeed(feed_url: string, category_id?: string, title?: string): Promise { return request('/feeds', { method: 'POST', body: JSON.stringify({ feed_url, category_id, title }), }); }, async updateFeed( id: string, data: { title?: string; category_id?: string; refresh_interval_minutes?: number; timeout_seconds?: number; } ): Promise { return request(`/feeds/${id}`, { method: 'PUT', body: JSON.stringify(data), }); }, async deleteFeed(id: string): Promise { return request(`/feeds/${id}`, { method: 'DELETE' }); }, async refreshFeed(id: string): Promise { return request(`/feeds/${id}/refresh`, { method: 'POST' }); }, async refreshAllFeeds(): Promise { return request('/feeds/refresh-all', { method: 'POST' }); }, // Entries async listEntries(params: { view?: string; feed_id?: string; category_id?: string; search?: string; limit?: number; offset?: number; }): Promise<{ entries: FeedEntry[]; total: number; limit: number; offset: number }> { const query = new URLSearchParams(); if (params.view) query.set('view', params.view); if (params.feed_id) query.set('feed_id', params.feed_id); if (params.category_id) query.set('category_id', params.category_id); if (params.search) query.set('search', params.search); if (params.limit) query.set('limit', params.limit.toString()); if (params.offset) query.set('offset', params.offset.toString()); return request(`/entries?${query.toString()}`); }, async updateRead(id: string, is_read: boolean): Promise { return request(`/entries/${id}/read`, { method: 'PUT', body: JSON.stringify({ is_read }), }); }, async updateStar(id: string, is_starred: boolean): Promise { return request(`/entries/${id}/star`, { method: 'PUT', body: JSON.stringify({ is_starred }), }); }, async markAllRead(feed_id?: string, category_id?: string): Promise { return request('/entries/mark-all-read', { method: 'POST', body: JSON.stringify({ feed_id, category_id }), }); }, // OPML async importOPML(file: File): Promise<{ imported_count: number; total_parsed: number }> { const token = localStorage.getItem('rsspipe_jwt'); const formData = new FormData(); formData.append('file', file); const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${API_BASE}/opml/import`, { method: 'POST', headers, body: formData, }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || 'Failed to import OPML'); } return res.json(); }, exportOPMLUrl(): string { const token = localStorage.getItem('rsspipe_jwt'); return `${API_BASE}/opml/export?token=${token || ''}`; } };