/v2/db/query — SELECT SQL-endpoint для ADMIN_EMAIL. Версия 0.1.22
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ipwhitelist",
|
"name": "ipwhitelist",
|
||||||
"version": "0.1.21",
|
"version": "0.1.22",
|
||||||
"description": "IP WhiteList microservice for cloud provider",
|
"description": "IP WhiteList microservice for cloud provider",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -136,6 +136,30 @@ function createV2Router({ generateCsrfToken, doubleCsrfProtection } = {}) {
|
|||||||
res.send(renderDbStatus(info));
|
res.send(renderDbStatus(info));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── GET /v2/db/query — SQL-запросы к БД (только ADMIN_EMAIL) ─────
|
||||||
|
router.get('/db/query', resolveContext, async (req, res) => {
|
||||||
|
if (!req.canQueryDB) return res.status(403).send('Доступ запрещён — только ADMIN_EMAIL');
|
||||||
|
const sqlParam = req.query.sql || '';
|
||||||
|
const { pool } = require('./src/db');
|
||||||
|
let result = null;
|
||||||
|
let error = null;
|
||||||
|
let elapsed = 0;
|
||||||
|
|
||||||
|
if (sqlParam) {
|
||||||
|
try {
|
||||||
|
const start = Date.now();
|
||||||
|
const r = await pool.query('SELECT ' + sqlParam);
|
||||||
|
elapsed = Date.now() - start;
|
||||||
|
result = { rows: r.rows, rowCount: r.rowCount, fields: r.fields.map(f => f.name) };
|
||||||
|
} catch (e) {
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
res.send(renderDbQuery(sqlParam, result, error, elapsed));
|
||||||
|
});
|
||||||
|
|
||||||
// ── GET /v2/iam-gateway-test — сравнение API (только админ) ────────
|
// ── GET /v2/iam-gateway-test — сравнение API (только админ) ────────
|
||||||
router.get('/iam-gateway-test', resolveContext, async (req, res) => {
|
router.get('/iam-gateway-test', resolveContext, async (req, res) => {
|
||||||
if (!req.canAdmin) return res.status(403).send('Доступ запрещён');
|
if (!req.canAdmin) return res.status(403).send('Доступ запрещён');
|
||||||
@@ -384,4 +408,24 @@ function renderDbStatus(info) {
|
|||||||
+ '</body></html>';
|
+ '</body></html>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderDbQuery(sql, result, error, elapsed) {
|
||||||
|
return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>SQL Query</title>'
|
||||||
|
+ '<style>body{font-family:monospace;background:#111;color:#eee;padding:20px}'
|
||||||
|
+ 'h2{color:#4fc3f7}input{width:600px;padding:10px;font-family:monospace;background:#1a1a2e;color:#eee;border:1px solid #333;border-radius:6px;font-size:14px}'
|
||||||
|
+ 'button{padding:10px 20px;background:#4fc3f7;color:#000;border:none;border-radius:6px;cursor:pointer;font-weight:bold;font-size:14px}'
|
||||||
|
+ '.err{color:#ef5350}.ok{color:#81c784}'
|
||||||
|
+ 'pre{background:#1a1a2e;padding:10px;border-radius:6px;overflow-x:auto;max-height:600px;font-size:12px}'
|
||||||
|
+ 'table{border-collapse:collapse;margin:10px 0;font-size:12px}td,th{border:1px solid #333;padding:4px 10px;text-align:left}th{background:#1a1a2e;color:#4fc3f7}'
|
||||||
|
+ '</style></head><body>'
|
||||||
|
+ '<h2>🔍 SQL Query <span style="font-size:14px;color:#888">(SELECT prefix)</span></h2>'
|
||||||
|
+ '<form method="get"><input name="sql" value="' + (sql || '').replace(/"/g,'"') + '" placeholder="* FROM companies"><button>Execute</button></form>'
|
||||||
|
+ (error ? '<p class="err" style="margin-top:10px">❌ ' + error + '</p>' : '')
|
||||||
|
+ (result ? '<p style="margin-top:10px"><span class="ok">✅ ' + result.rowCount + ' rows</span> (' + elapsed + 'ms)</p>'
|
||||||
|
+ '<table><tr>' + result.fields.map(f => '<th>' + f + '</th>').join('') + '</tr>'
|
||||||
|
+ result.rows.map(r => '<tr>' + result.fields.map(f => '<td>' + (r[f] === null ? '<i>null</i>' : String(r[f])) + '</td>').join('') + '</tr>').join('')
|
||||||
|
+ '</table>' : '')
|
||||||
|
+ '<p style="margin-top:20px"><a href="/v2/db-status" style="color:#4fc3f7">📊 PG Diagnostic</a></p>'
|
||||||
|
+ '</body></html>';
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { createV2Router };
|
module.exports = { createV2Router };
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
version: '0.1.21',
|
version: '0.1.22',
|
||||||
|
|
||||||
// ── Среда ─────────────────────────────────────────────────────────────────
|
// ── Среда ─────────────────────────────────────────────────────────────────
|
||||||
// APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API)
|
// APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API)
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ function applyContext(req, u) {
|
|||||||
req.companyName = u.companyName || req.clientId;
|
req.companyName = u.companyName || req.clientId;
|
||||||
const allCids = u.allClientIds || [u.clientId || ''];
|
const allCids = u.allClientIds || [u.clientId || ''];
|
||||||
req.canAdmin = allCids.includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin || u.email === (process.env.ADMIN_EMAIL || '');
|
req.canAdmin = allCids.includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin || u.email === (process.env.ADMIN_EMAIL || '');
|
||||||
|
req.canQueryDB = u.email === (process.env.ADMIN_EMAIL || ''); // SQL-доступ только для ADMIN_EMAIL
|
||||||
req.adminMode = req.canAdmin;
|
req.adminMode = req.canAdmin;
|
||||||
req.isAdmin = req.canAdmin;
|
req.isAdmin = req.canAdmin;
|
||||||
req.isImpersonated = isImpersonated;
|
req.isImpersonated = isImpersonated;
|
||||||
|
|||||||
Reference in New Issue
Block a user