// API клиент const API_BASE = window.API_BASE || 'https://api.buhapp.mygoodservice.ru'; const Store = { access: null, refresh: null, user: null, save(tokens) { this.access = tokens.access; this.refresh = tokens.refresh; try { localStorage.setItem('buhapp', JSON.stringify({ access, refresh, user: this.user })); } catch {} }, load() { try { const s = JSON.parse(localStorage.getItem('buhapp') || 'null'); if (s) { this.access = s.access; this.refresh = s.refresh; this.user = s.user; } } catch {} return this.access; }, clear() { this.access = null; this.refresh = null; this.user = null; try { localStorage.removeItem('buhapp'); } catch {} }, }; async function api(path, opts = {}) { opts.headers = opts.headers || {}; if (Store.access && !opts.headers.Authorization) { opts.headers.Authorization = `Bearer ${Store.access}`; } if (opts.body && typeof opts.body === 'object' && !(opts.body instanceof FormData)) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(opts.body); } let r = await fetch(API_BASE + path, opts); if (r.status === 401 && Store.refresh) { // refresh const r2 = await fetch(API_BASE + '/api/v1/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh: Store.refresh }), }); if (r2.ok) { const tokens = await r2.json(); Store.save(tokens); opts.headers.Authorization = `Bearer ${tokens.access}`; r = await fetch(API_BASE + path, opts); } else { Store.clear(); } } const text = await r.text(); let data = null; try { data = text ? JSON.parse(text) : null; } catch { data = text; } if (!r.ok) { const msg = (data && data.error) || `HTTP ${r.status}`; const err = new Error(msg); err.status = r.status; err.data = data; throw err; } return data; } // === Auth === const Auth = { async loginWithTelegram(initData) { const r = await api('/api/v1/auth/telegram', { method: 'POST', body: { init_data: initData }, }); Store.save(r.tokens); Store.user = r.user; return r; }, async registerEmail(data) { const r = await api('/api/v1/auth/register', { method: 'POST', body: data }); Store.save(r.tokens); Store.user = r.user; return r; }, async loginEmail(email, password) { const r = await api('/api/v1/auth/login', { method: 'POST', body: { email, password } }); Store.save(r.tokens); Store.user = r.user; return r; }, async me() { const r = await api('/api/v1/me'); Store.user = r.user; return r; }, async updateMe(p) { return api('/api/v1/me', { method: 'PUT', body: p }); }, async getPrefs() { return api('/api/v1/me/prefs'); }, async updatePrefs(p) { return api('/api/v1/me/prefs', { method: 'PUT', body: p }); }, async updateLocation(lat, lng, visible) { return api('/api/v1/me/location', { method: 'PUT', body: { lat, lng, visible } }); }, async setVisibility(v) { return api('/api/v1/me/visibility', { method: 'PUT', body: { visible: v } }); }, async searchNearby(lat, lng, radiusKm) { return api('/api/v1/search/nearby?lat=' + lat + '&lng=' + lng + '&radius=' + radiusKm); }, async getUser(id) { return api('/api/v1/users/' + id); }, }; // === Chat === const ChatApi = { async ensureChat(otherId) { return api('/api/v1/chats', { method: 'POST', body: { other_id: otherId } }); }, async listChats() { return api('/api/v1/chats'); }, async listMessages(chatId) { return api('/api/v1/chats/' + chatId + '/messages'); }, async sendMessage(chatId, body, photoUrl) { return api('/api/v1/chats/' + chatId + '/messages', { method: 'POST', body: { body, photo_url: photoUrl } }); }, async editMessage(msgId, body) { return api('/api/v1/messages/' + msgId, { method: 'PUT', body: { body } }); }, async deleteMessage(msgId) { return api('/api/v1/messages/' + msgId, { method: 'DELETE' }); }, async markRead(chatId) { return api('/api/v1/chats/' + chatId + '/read', { method: 'PUT', body: {} }); }, async block(userId) { return api('/api/v1/blocks', { method: 'POST', body: { user_id: userId } }); }, async report(type, id, reason) { return api('/api/v1/reports', { method: 'POST', body: { target_type: type, target_id: id, reason } }); }, }; // === Reviews === const ReviewApi = { async create(chatId, rating, body, anonymous) { return api('/api/v1/reviews', { method: 'POST', body: { chat_id: chatId, rating, body, anonymous } }); }, async listForUser(userId) { return api('/api/v1/users/' + userId + '/reviews'); }, async stats(userId) { return api('/api/v1/users/' + userId + '/stats'); }, }; window.API = { api, Auth, ChatApi, ReviewApi, Store };