'use strict'; /** * ui/api-client.js — HTTP-клиент к /api/v1/*. * * UI-роуты не ходят напрямую в БД — только сюда. * Токен берётся из req.session.token (установлен на /login). * * Все ошибки пробрасываются как Error с понятным message. */ const http = require('http'); const https = require('https'); const API_BASE = process.env.API_BASE || 'http://localhost:' + (process.env.PORT || 3000); /** * Выполнить запрос к API. * @param {string} method — GET, POST, PATCH, DELETE * @param {string} path — /api/v1/entries * @param {string} token — Bearer token * @param {object} [body] — JSON body (для POST/PATCH) * @returns {Promise<{status: number, data: any}>} */ function apiRequest(method, path, token, body, impHeaders) { return new Promise((resolve, reject) => { const url = new URL(API_BASE + path); const lib = url.protocol === 'https:' ? https : http; const bodyStr = body ? JSON.stringify(body) : null; const opts = { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, method, headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json', }, }; // Проброс контекста имперсонации из UI в API-слой if (impHeaders) { if (impHeaders.impEmail) opts.headers['X-Imp-Email'] = impHeaders.impEmail; if (impHeaders.impOriginalEmail) opts.headers['X-Imp-OriginalEmail'] = impHeaders.impOriginalEmail; if (impHeaders.impClientId) opts.headers['X-Imp-ClientId'] = impHeaders.impClientId; } if (bodyStr) { opts.headers['Content-Type'] = 'application/json'; opts.headers['Content-Length'] = Buffer.byteLength(bodyStr); } const req = lib.request(opts, (res) => { let raw = ''; res.on('data', c => { raw += c; }); res.on('end', () => { const ct = res.headers['content-type'] || ''; try { const data = ct.includes('application/json') ? JSON.parse(raw) : raw; resolve({ status: res.statusCode, data }); } catch (e) { reject(new Error('Invalid JSON from API: ' + e.message)); } }); }); req.setTimeout(15000, () => { req.destroy(); reject(new Error('API timeout')); }); req.on('error', reject); if (bodyStr) req.write(bodyStr); req.end(); }); } // Хелперы — один вызов на метод const api = { get: (path, token, imp) => apiRequest('GET', path, token, null, imp), post: (path, token, body, imp) => apiRequest('POST', path, token, body, imp), patch: (path, token, body, imp) => apiRequest('PATCH', path, token, body, imp), delete: (path, token, imp) => apiRequest('DELETE', path, token, null, imp), // Достать Bearer из сессии token: (req) => req.session && req.session.token, // Построить заголовки имперсонации из req.user (для проброса в API-слой) impHeaders: (req) => { if (!req.user || !req.user.isImpersonated) return null; return { impEmail: req.user.email, impOriginalEmail: req.user.originalUserEmail, impClientId: req.user.activeClientId || req.user.clientId, }; }, }; module.exports = api;