94 lines
3.4 KiB
JavaScript
94 lines
3.4 KiB
JavaScript
'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;
|