v2: подробные комментарии во всех модулях — что, зачем, контракты

This commit is contained in:
2026-06-13 07:37:07 +04:00
parent 1af304b2a8
commit 616b4ead09
14 changed files with 592 additions and 518 deletions
+34 -12
View File
@@ -1,21 +1,41 @@
// ═══════════════════════════════════════════════════════════════════════════════
// V2 — пользовательский CRUD (фронтенд, только Express)
// V2 — Фронтенд обычного пользователя
//
// Вход: req.clientId, req.email, req.impersonatedBy из resolveContext.
// Всегда показывает выбор компании (даже если одна).
// НЕ работает с БД напрямую — только через crud API-слой.
// ЭТО: Express-роутер, который рендерит HTML и принимает POST-формы.
// НЕ: работа с БД (только через crud/), валидация (в crud/).
//
// ЗАЧЕМ:
// 1. Единственное место где генерится HTML для юзера.
// 2. Все действия юзера (add/edit/delete) — через POST-формы.
// 3. Выбор компании — всегда выпадающий список (даже если одна компания).
// 4. Сообщения об ошибках/успехе — через query-параметры (?msg=, ?error=).
//
// КОНТЕКСТ (из resolveContext middleware):
// req.clientId — W-номер текущей компании
// req.email — почта юзера
// req.impersonatedBy — null или почта админа
// req.profiles — список компаний для выпадающего списка
//
// МАРШРУТЫ:
// GET / — список записей + форма добавления
// POST /add — добавить CIDR (→ crud.add → 302 с ?msg=)
// POST /edit/:id — изменить запись
// POST /delete/:id — удалить запись (soft delete)
// ═══════════════════════════════════════════════════════════════════════════════
const express = require('express');
const crud = require('../crud');
const crud = require('../crud'); // ← единственная зависимость от данных
function createUserRouter() {
const router = express.Router();
// ── GET / — главная: список записей + форма ────────────────────────────
// ── GET / — главная страница ──────────────────────────────────────────
router.get('/', async (req, res) => {
try {
const clId = req.clientId;
// Список компаний для выпадающего списка
// Если profiles пуст — создаём fallback из clientId (одна компания)
const allCompanies = req.profiles.length > 0
? req.profiles
: [{ client_id: clId, company_name: req.companyName || clId }];
@@ -23,7 +43,7 @@ function createUserRouter() {
const includeDeleted = req.query.deleted === '1';
const { entries, used, limit } = await crud.list(clId, includeDeleted);
// Переключение компании
// Переключение компании: запоминаем в сессии и редиректим
if (req.query.switchTo && allCompanies.find(c => c.client_id === req.query.switchTo)) {
req.session.user.activeClientId = req.query.switchTo;
return res.redirect('/v2/app');
@@ -35,18 +55,19 @@ function createUserRouter() {
}
});
// ── POST /add ───────────────────────────────────────────────────────────
// ── POST /add — добавить запись ───────────────────────────────────────
router.post('/add', async (req, res) => {
try {
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) {
// Ошибка валидации/лимита/дубликата — показываем юзеру
res.redirect('/v2/app?error=' + encodeURIComponent(e.message));
}
});
// ── POST /edit/:id ──────────────────────────────────────────────────────
// ── POST /edit/:id — изменить запись ──────────────────────────────────
router.post('/edit/:id', async (req, res) => {
try {
const result = await crud.edit(parseInt(req.params.id), req.clientId, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy);
@@ -57,7 +78,7 @@ function createUserRouter() {
}
});
// ── POST /delete/:id ────────────────────────────────────────────────────
// ── POST /delete/:id — удалить запись (soft delete) ───────────────────
router.post('/delete/:id', async (req, res) => {
try {
await crud.remove(parseInt(req.params.id), req.clientId, req.email, req.impersonatedBy);
@@ -70,8 +91,9 @@ function createUserRouter() {
return router;
}
// ── HTML-рендеринг (временный, будет заменён на EJS) ─────────────────────
// ── renderPage() → HTML ─────────────────────────────────────────────────────
// Временный inline HTML (будет заменён на EJS-шаблоны).
// Данные: entries, limit, used, allCompanies, clId, user, includeDeleted.
function renderPage({ entries, limit, used, allCompanies, clId, user, includeDeleted }) {
const companyOptions = allCompanies.map(c =>
`<option value="${c.client_id}" ${c.client_id === clId ? 'selected' : ''}>${c.company_name || c.client_id} (${c.client_id})</option>`