From 573aa99c7d643f7a5c3007493b0594dc3f5ec86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 13 Jun 2026 06:55:18 +0400 Subject: [PATCH] =?UTF-8?q?v2:=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BB=D0=BE=D1=91=D0=B2=20?= =?UTF-8?q?=E2=80=94=20crud/index.js=20API-=D1=81=D0=BB=D0=BE=D0=B9,=20use?= =?UTF-8?q?r/=20=D0=B8=20test/=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20crud?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- v2/src/crud/index.js | 242 ++++++------------------------------------- v2/src/test/index.js | 22 ++-- v2/src/user/index.js | 29 ++---- 4 files changed, 56 insertions(+), 239 deletions(-) diff --git a/package.json b/package.json index df8b4f7..143fd8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.89", + "version": "0.5.90", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/v2/src/crud/index.js b/v2/src/crud/index.js index 35b0445..93a21d4 100644 --- a/v2/src/crud/index.js +++ b/v2/src/crud/index.js @@ -1,217 +1,43 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — CRUD-роутер (только для авторизованных) +// V2 — CRUD API-слой (чистые функции, без Express) // -// Вход: sessionUser из v2_ сессии (email, clientId, allClientIds, profiles, ...) -// Всегда показывает выбор компании (даже если одна). -// Переключение — смена activeClientId в сессии (без IAM). +// Принимает clientId (W-номер), сам резолвит companyId через getOrCreateCompany. +// Фронтенд (user/) и тесты (test/) дергают эти функции. +// +// Сигнатуры: +// list(clientId) → { entries, used, limit } +// add(clientId, cidr, comment, email, impBy) → { entry, wasNormalized } +// edit(entryId, clientId, cidr, comment, email, impBy) → { entry, wasNormalized } +// remove(entryId, clientId, email, impBy) → void // ═══════════════════════════════════════════════════════════════════════════════ -const express = require('express'); -const q = require('../db/queries'); -const config = require('../config'); -const { validate } = require('../validators'); +const q = require('../db/queries'); -function createCrudRouter() { - const router = express.Router(); - - // ── middleware: только для залогиненных ───────────────────────────────── - router.use((req, res, next) => { - if (!req.session.user) return res.redirect('/v2/login'); - next(); - }); - - // ── helpers ────────────────────────────────────────────────────────────── - function user(req) { return req.session.user; } - function token(req) { return req.session.token; } - - // activeClientId — текущая выбранная компания - function activeClientId(req) { - return req.session.user.activeClientId || req.session.user.clientId; - } - - // Список компаний для выпадающего списка - function companies(req) { - const u = user(req); - if (u.profiles && u.profiles.length > 0) return u.profiles; - // fallback: одна компания из clientId - return [{ client_id: u.clientId, company_name: u.companyName || u.clientId, is_active_profile: true }]; - } - - // ── GET /v2/app — главная страница ────────────────────────────────────── - router.get('/', async (req, res) => { - try { - const clId = activeClientId(req); - const allCompanies = companies(req); - const company = await q.getOrCreateCompany(clId, clId); - - const includeDeleted = req.query.deleted === '1'; - const entries = await q.listEntries(company.id, includeDeleted); - const limit = await q.getLimit(company); - const used = entries.filter(e => !e.deleted_at).length; - - const switchTo = req.query.switchTo; - if (switchTo && allCompanies.find(c => c.client_id === switchTo)) { - req.session.user.activeClientId = switchTo; - return res.redirect('/v2/app'); - } - - res.send(renderApp({ entries, company, limit, used, allCompanies, clId, user: user(req), includeDeleted })); - } catch (e) { - res.status(500).send('

Ошибка

' + e.message + '
Назад'); - } - }); - - // ── POST /v2/app/add ──────────────────────────────────────────────────── - router.post('/add', async (req, res) => { - try { - const clId = activeClientId(req); - const company = await q.getOrCreateCompany(clId, clId); - const u = user(req); - - const result = await q.createEntry( - company.id, - req.body.cidr || '', - req.body.comment || '', - u.email, - u.originalUserEmail || null // impersonated_by - ); - - const msg = result.wasNormalized ? 'Добавлено (адрес нормализован)' : 'Добавлено'; - res.redirect('/v2/app?msg=' + encodeURIComponent(msg)); - } catch (e) { - res.redirect('/v2/app?error=' + encodeURIComponent(e.message)); - } - }); - - // ── POST /v2/app/edit/:id ─────────────────────────────────────────────── - router.post('/edit/:id', async (req, res) => { - try { - const clId = activeClientId(req); - const company = await q.getOrCreateCompany(clId, clId); - const u = user(req); - - const result = await q.updateEntry( - parseInt(req.params.id), - company.id, - req.body.cidr || '', - req.body.comment || '', - u.email, - u.originalUserEmail || null - ); - - const msg = result.wasNormalized ? 'Изменено (адрес нормализован)' : 'Изменено'; - res.redirect('/v2/app?msg=' + encodeURIComponent(msg)); - } catch (e) { - res.redirect('/v2/app?error=' + encodeURIComponent(e.message)); - } - }); - - // ── POST /v2/app/delete/:id ───────────────────────────────────────────── - router.post('/delete/:id', async (req, res) => { - try { - const clId = activeClientId(req); - const company = await q.getOrCreateCompany(clId, clId); - const u = user(req); - - await q.deleteEntry( - parseInt(req.params.id), - company.id, - u.email, - u.originalUserEmail || null - ); - - res.redirect('/v2/app?msg=' + encodeURIComponent('Удалено')); - } catch (e) { - res.redirect('/v2/app?error=' + encodeURIComponent(e.message)); - } - }); - - return router; +async function resolve(clientId) { + return q.getOrCreateCompany(clientId, clientId); } -// ── HTML-рендеринг (временный, без EJS) ───────────────────────────────────── - -function renderApp({ entries, company, limit, used, allCompanies, clId, user, includeDeleted }) { - const msg = (s) => s ? `
${s}
` : ''; - const err = (s) => s ? `
${s}
` : ''; - - const companyOptions = allCompanies.map(c => - `` - ).join(''); - - const rows = entries.map(e => { - const del = e.deleted_at; - const style = del ? 'text-decoration:line-through;opacity:0.5' : ''; - const actions = del ? 'удалено' : ` -
- - - -
-
- -
`; - return ` - ${e.value_cidr}${e.comment || ''}${e.created_by} - ${new Date(e.created_at).toLocaleString('ru')} - ${actions} - `; - }).join(''); - - return ` -V2 — IP WhiteList - - -
- V2 IP WhiteList v${config.version} - ${user.email} | ${user.fio || ''} ${user.isAdmin ? '| ADMIN' : ''} - -
- -
- Выйти -
-
- -${msg('')}${err('')} - - - -

Записей: ${used} из ${limit}

- -
-
- - - -
-
- - - - ${rows || ''} -
CIDRКомментарийКтоКогда
Нет записей
- -

${includeDeleted ? 'Скрыть удалённые' : 'Показать удалённые'}

- -`; +async function list(clientId, includeDeleted = false) { + const co = await resolve(clientId); + const entries = await q.listEntries(co.id, includeDeleted); + const limit = await q.getLimit(co); + const used = entries.filter(e => !e.deleted_at).length; + return { entries, used, limit }; } -module.exports = { createCrudRouter }; +async function add(clientId, cidr, comment, email, impBy) { + const co = await resolve(clientId); + return q.createEntry(co.id, cidr, comment, email, impBy); +} + +async function edit(entryId, clientId, cidr, comment, email, impBy) { + const co = await resolve(clientId); + return q.updateEntry(entryId, co.id, cidr, comment, email, impBy); +} + +async function remove(entryId, clientId, email, impBy) { + const co = await resolve(clientId); + return q.deleteEntry(entryId, co.id, email, impBy); +} + +module.exports = { list, add, edit, remove }; diff --git a/v2/src/test/index.js b/v2/src/test/index.js index 28ad3cf..f2f0e72 100644 --- a/v2/src/test/index.js +++ b/v2/src/test/index.js @@ -4,6 +4,7 @@ // ═══════════════════════════════════════════════════════════════════════════════ const express = require('express'); +const crud = require('../crud'); const q = require('../db/queries'); const { pool } = require('../db'); @@ -30,25 +31,25 @@ function createTestRouter() { switch (action) { // ── LIST ── - case 'list': - const entries = await q.listEntries(company.id, req.query.deleted === '1'); - return res.json({ ok: true, entries, count: entries.length, company: { id: company.id, client_id: company.client_id } }); + case 'list': { + const { entries, used, limit } = await crud.list(clientId, req.query.deleted === '1'); + return res.json({ ok: true, entries, count: entries.length, used, limit }); } // ── ADD ── case 'add': { - const r = await q.createEntry(company.id, req.query.cidr || '', req.query.comment || '', email, impBy); + const r = await crud.add(clientId, req.query.cidr || '', req.query.comment || '', email, impBy); return res.json({ ok: true, entry: r.entry, wasNormalized: r.wasNormalized }); } // ── EDIT ── case 'edit': { - const r = await q.updateEntry(parseInt(req.query.id), company.id, req.query.cidr || '', req.query.comment || '', email, impBy); + const r = await crud.edit(parseInt(req.query.id), clientId, req.query.cidr || '', req.query.comment || '', email, impBy); return res.json({ ok: true, entry: r.entry, wasNormalized: r.wasNormalized }); } // ── DELETE ── case 'delete': - await q.deleteEntry(parseInt(req.query.id), company.id, email, impBy); + await crud.remove(parseInt(req.query.id), clientId, email, impBy); return res.json({ ok: true }); // ── AUDIT ── @@ -112,10 +113,9 @@ function createTestRouter() { const impBy = idx === 3 ? 'real@nubes.ru' : null; const prefix = cidPrefix[idx]; tasks.push((async () => { - const co = await q.getOrCreateCompany(clientId, clientId); for (let j = 0; j < perCompany; j++) { try { - await q.createEntry(co.id, prefix + '.0.' + j + '.0/24', 'chaos', email, impBy); + await crud.add(clientId, prefix + '.0.' + j + '.0/24', 'chaos', email, impBy); ok++; } catch (e) { errors++; } } @@ -127,12 +127,10 @@ function createTestRouter() { const checks = []; let totalEntries = 0, totalAudit = 0, anyDupes = false, anyOverLimit = false; for (const clientId of cids) { - const co = await q.getOrCreateCompany(clientId, clientId); - const entries = await q.listEntries(co.id); + const { entries, limit } = await crud.list(clientId); const cidrs = entries.map(e => e.value_cidr); const dupes = cidrs.length !== new Set(cidrs).size; - const limit = await q.getLimit(co); - const audit = await q.getAudit(co.id); + const audit = await q.getAudit((await q.getOrCreateCompany(clientId, clientId)).id); totalEntries += entries.length; totalAudit += audit.length; if (dupes) anyDupes = true; diff --git a/v2/src/user/index.js b/v2/src/user/index.js index b1a6c45..f38b31e 100644 --- a/v2/src/user/index.js +++ b/v2/src/user/index.js @@ -1,11 +1,13 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — пользовательский CRUD -// Вход: req.v2_* из resolveContext (email, clientId, allClientIds, profiles, ...) +// V2 — пользовательский CRUD (фронтенд, только Express) +// +// Вход: req.clientId, req.email, req.impersonatedBy из resolveContext. // Всегда показывает выбор компании (даже если одна). +// НЕ работает с БД напрямую — только через crud API-слой. // ═══════════════════════════════════════════════════════════════════════════════ const express = require('express'); -const q = require('../db/queries'); +const crud = require('../crud'); function createUserRouter() { const router = express.Router(); @@ -18,11 +20,8 @@ function createUserRouter() { ? req.profiles : [{ client_id: clId, company_name: req.companyName || clId }]; - const company = await q.getOrCreateCompany(clId, allCompanies.find(c => c.client_id === clId)?.company_name || clId); const includeDeleted = req.query.deleted === '1'; - const entries = await q.listEntries(company.id, includeDeleted); - const limit = await q.getLimit(company); - const used = entries.filter(e => !e.deleted_at).length; + const { entries, used, limit } = await crud.list(clId, includeDeleted); // Переключение компании if (req.query.switchTo && allCompanies.find(c => c.client_id === req.query.switchTo)) { @@ -30,7 +29,7 @@ function createUserRouter() { return res.redirect('/v2/app'); } - res.send(renderPage({ entries, company, limit, used, allCompanies, clId, user: req.user, includeDeleted })); + res.send(renderPage({ entries, limit, used, allCompanies, clId, user: req.user, includeDeleted })); } catch (e) { res.status(500).send('

Ошибка

' + e.message + '
Назад'); } @@ -39,9 +38,7 @@ function createUserRouter() { // ── POST /add ─────────────────────────────────────────────────────────── router.post('/add', async (req, res) => { try { - const clId = req.clientId; - const company = await q.getOrCreateCompany(clId, clId); - const result = await q.createEntry(company.id, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy); + const result = await crud.add(req.clientId, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy); const msg = result.wasNormalized ? 'Добавлено (адрес нормализован)' : 'Добавлено'; res.redirect('/v2/app?msg=' + encodeURIComponent(msg)); } catch (e) { @@ -52,9 +49,7 @@ function createUserRouter() { // ── POST /edit/:id ────────────────────────────────────────────────────── router.post('/edit/:id', async (req, res) => { try { - const clId = req.clientId; - const company = await q.getOrCreateCompany(clId, clId); - const result = await q.updateEntry(parseInt(req.params.id), company.id, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy); + const result = await crud.edit(parseInt(req.params.id), req.clientId, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy); const msg = result.wasNormalized ? 'Изменено (адрес нормализован)' : 'Изменено'; res.redirect('/v2/app?msg=' + encodeURIComponent(msg)); } catch (e) { @@ -65,9 +60,7 @@ function createUserRouter() { // ── POST /delete/:id ──────────────────────────────────────────────────── router.post('/delete/:id', async (req, res) => { try { - const clId = req.clientId; - const company = await q.getOrCreateCompany(clId, clId); - await q.deleteEntry(parseInt(req.params.id), company.id, req.email, req.impersonatedBy); + await crud.remove(parseInt(req.params.id), req.clientId, req.email, req.impersonatedBy); res.redirect('/v2/app?msg=Удалено'); } catch (e) { res.redirect('/v2/app?error=' + encodeURIComponent(e.message)); @@ -79,7 +72,7 @@ function createUserRouter() { // ── HTML-рендеринг (временный, будет заменён на EJS) ───────────────────── -function renderPage({ entries, company, limit, used, allCompanies, clId, user, includeDeleted }) { +function renderPage({ entries, limit, used, allCompanies, clId, user, includeDeleted }) { const companyOptions = allCompanies.map(c => `` ).join('');