// Same-origin API wrapper (api проксируется через nginx на тот же домен) // Никаких retry/timeout на login — только UI feedback const DEFAULT_INIT = { credentials: 'include', cache: 'no-store', redirect: 'follow', }; export async function api(path, options = {}) { const { json, headers: extraHeaders, ...rest } = options; const headers = new Headers(extraHeaders || {}); let body = rest.body; if (json !== undefined) { headers.set('Content-Type', 'application/json'); body = JSON.stringify(json); } if (!headers.has('Accept')) { headers.set('Accept', 'application/json'); } const res = await fetch(path, { ...DEFAULT_INIT, ...rest, headers, body, }); const ct = res.headers.get('content-type') || ''; const isJson = ct.includes('application/json'); const data = isJson ? await res.json().catch(() => null) : await res.text(); if (!res.ok) { const err = new Error((data && (data.message || data.error)) || `HTTP ${res.status}`); err.status = res.status; err.data = data; throw err; } return data; } export const apiGet = (path, opts) => api(path, { ...opts, method: 'GET' }); export const apiPost = (path, json, opts) => api(path, { ...opts, method: 'POST', json }); export const apiPut = (path, json, opts) => api(path, { ...opts, method: 'PUT', json }); export const apiDelete = (path, opts) => api(path, { ...opts, method: 'DELETE' }); // Совместимость со старым window.API.Auth window.API = { api, apiGet, apiPost, apiPut, apiDelete, // обёртки для совместимости со старыми view Auth: { loginEmail: (email, password) => apiPost('/api/v1/auth/login', { email, password }), registerEmail: (data) => apiPost('/api/v1/auth/register', data), me: () => apiGet('/api/v1/me'), updateMe: (p) => apiPut('/api/v1/me', p), getPrefs: () => apiGet('/api/v1/me/prefs'), updatePrefs: (p) => apiPut('/api/v1/me/prefs', p), updateLocation: (lat, lng, visible) => apiPut('/api/v1/me/location', { lat, lng, visible }), setVisibility: (v) => apiPut('/api/v1/me/visibility', { visible: v }), searchNearby: (lat, lng, radius) => apiGet(`/api/v1/search/nearby?lat=${lat}&lng=${lng}&radius=${radius}`), getUser: (id) => apiGet('/api/v1/users/' + id), }, ChatApi: { ensureChat: (otherId) => apiPost('/api/v1/chats', { other_id: otherId }), listChats: () => apiGet('/api/v1/chats'), listMessages: (chatId) => apiGet('/api/v1/chats/' + chatId + '/messages'), sendMessage: (chatId, body, photoUrl) => apiPost('/api/v1/chats/' + chatId + '/messages', { body, photo_url: photoUrl }), editMessage: (msgId, body) => apiPut('/api/v1/messages/' + msgId, { body }), deleteMessage: (msgId) => apiDelete('/api/v1/messages/' + msgId), markRead: (chatId) => apiPut('/api/v1/chats/' + chatId + '/read', {}), block: (userId) => apiPost('/api/v1/blocks', { user_id: userId }), report: (type, id, reason) => apiPost('/api/v1/reports', { target_type: type, target_id: id, reason }), }, ReviewApi: { create: (chatId, rating, body, anonymous) => apiPost('/api/v1/reviews', { chat_id: chatId, rating, body, anonymous }), listForUser: (userId) => apiGet('/api/v1/users/' + userId + '/reviews'), stats: (userId) => apiGet('/api/v1/users/' + userId + '/stats'), }, ConsentApi: { status: () => apiGet('/api/v1/auth/consents').catch(() => ({ all_accepted: false, accepted: {} })), accept: (payload) => apiPost('/api/v1/auth/consents', payload), }, PlacesApi: { nearby: (lat, lng, radius, limit = 30) => apiGet(`/api/v1/places/nearby?lat=${lat}&lng=${lng}&radius=${radius || 5}&limit=${limit}`).catch((e) => ({ places: [], count: 0, error: e.message })), }, Store: { access: null, refresh: null, user: null, save(tokens) { if (!tokens) return; this.access = tokens.Access || tokens.access; this.refresh = tokens.Refresh || tokens.refresh; }, load() { return this.access; }, clear() { this.access = null; this.refresh = null; this.user = null; }, }, };