// ======================================================================= // Notes Application - API Client Module (Native ES Module) // ======================================================================= // Persistent session: auto-inject Authorization header if token exists in localStorage const originalFetch = window.fetch; window.fetch = function(url, options = {}) { const token = localStorage.getItem('notes_token'); if (token && typeof url === 'string' && url.startsWith('/api/')) { options.headers = options.headers || {}; if (options.headers instanceof Headers) { if (!options.headers.has('Authorization')) { options.headers.set('Authorization', `Bearer ${token}`); } } else if (Array.isArray(options.headers)) { if (!options.headers.some(([k]) => k.toLowerCase() === 'authorization')) { options.headers.push(['Authorization', `Bearer ${token}`]); } } else { if (!options.headers['Authorization']) { options.headers['Authorization'] = `Bearer ${token}`; } } } return originalFetch.call(this, url, options); }; export const api = { // Auth endpoints auth: { async check() { const resp = await fetch('/api/me'); if (resp.ok) { return await resp.json(); } return null; }, async login(username, password) { return await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); }, async logout() { return await fetch('/api/logout', { method: 'POST' }); }, async changePassword(currentPassword, newPassword) { return await fetch('/api/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ old_password: currentPassword, new_password: newPassword }) }); } }, // Cards & Vocabulary cards: { async getAll(params = {}) { const qs = new URLSearchParams(params).toString(); const url = qs ? `/api/cards?${qs}` : '/api/cards'; const resp = await fetch(url); return resp.ok ? await resp.json() : []; }, async lookup(term) { return await api.lookup.search(term); }, async create(payload) { return await fetch('/api/cards', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); }, async update(id, payload) { return await fetch(`/api/cards/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); }, async delete(id) { return await fetch(`/api/cards/${id}`, { method: 'DELETE' }); }, async batch(payload) { return await fetch('/api/cards/batch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } }, // Dictionary Lookup & AI Sentence Translation lookup: { async search(term) { const resp = await fetch(`/api/lookup?term=${encodeURIComponent(term)}`); if (resp.ok) { return await resp.json(); } throw new Error(`Lookup failed: ${resp.status}`); }, async translateSentence(sentence, targetWord = '') { const resp = await fetch('/api/translate-sentence', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sentence: sentence, target_word: targetWord }) }); if (resp.ok) { return await resp.json(); } throw new Error(`Sentence translation failed: ${resp.status}`); }, async getAiStatus() { const resp = await fetch('/api/ai/status'); return resp.ok ? await resp.json() : { online: false, models: [] }; } }, // Collections & Tags collections: { async getAll() { const resp = await fetch('/api/collections'); return resp.ok ? await resp.json() : []; } }, tags: { async getAll() { const resp = await fetch('/api/tags'); return resp.ok ? await resp.json() : []; } }, // Statistics & Analytics stats: { async getStats() { const resp = await fetch('/api/stats'); return resp.ok ? await resp.json() : null; }, async getSrsStats() { const resp = await fetch('/api/srs/stats'); return resp.ok ? await resp.json() : null; }, async getHeatmap(days = 180) { const resp = await fetch(`/api/srs/activity-heatmap?days=${days}`); return resp.ok ? await resp.json() : { days: [] }; } }, // SRS Spaced Repetition srs: { async getQueue(forceAll = false, tagFilter = null) { let url = '/api/srs/queue?limit=50'; if (forceAll) url += '&force_all=true'; if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`; const resp = await fetch(url); return resp.ok ? await resp.json() : []; }, async submitReview(cardId, rating) { return await fetch('/api/srs/review', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ card_id: cardId, rating }) }); } }, // Bookshelf & Reader books: { async getAll() { const resp = await fetch('/api/books'); return resp.ok ? await resp.json() : []; }, async get(id) { const resp = await fetch(`/api/books/${id}`); if (resp.ok) return await resp.json(); throw new Error(`Failed to load book ${id}: ${resp.status}`); }, async uploadFile(formData) { return await fetch('/api/books', { method: 'POST', body: formData }); }, async createManual(payload) { return await fetch('/api/books', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); }, async delete(id) { return await fetch(`/api/books/${id}`, { method: 'DELETE' }); }, async saveProgress(bookId, payload) { return await fetch(`/api/books/${bookId}/progress`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } } };