'use strict'; /** * ui/routes/entries.js — главная страница: список записей + CRUD. * * Данные из /api/v1/entries (Bearer token из сессии). * Admin: ?company= (числовой PK) для управления конкретной компанией. * - Без параметра: ищем компанию admin в списке по clientId. * Если не найдена (первый вход) — один GET /api/v1/entries создаёт её, * потом редирект на числовой ID. * - С параметром: грузим записи этой компании через admin-ветку API. * User: без параметра, API сам определяет компанию по clientId из токена. */ const { Router } = require('express'); const api = require('../api-client'); function createRouter() { const router = Router(); // URL для возврата после CRUD — всегда с числовым company ID function backUrl(req) { const id = req.query.company || (req.body && req.body.company_id); return id ? '/?company=' + id : '/'; } // Суффикс ?company= для API — только числовой ID или пусто function companyQuery(req) { const id = req.query.company || (req.body && req.body.company_id); if (id) return '?company=' + id; // Мульти-компания: передаём активный client_id if (!req.user.isAdmin && req.user.allClientIds && req.user.allClientIds.length > 1) { return '?client_id=' + encodeURIComponent(req.user.activeClientId || req.user.clientId); } return ''; } // GET / — список записей router.get('/', async (req, res) => { const token = api.token(req); // ── Переключатель компании через IAM ──────────────────────────────── const switchTo = (req.query.switchTo || '').trim(); if (!req.user.isAdmin && switchTo && req.user.profiles && req.user.profiles.length > 1) { const targetProfile = req.user.profiles.find(p => p.client_id === switchTo); if (targetProfile) { try { const { switchProfile } = require('../../src/auth'); await switchProfile(token, targetProfile.id); // Обновляем сессию: новый активный профиль req.session.user.activeClientId = switchTo; req.session.user.clientId = switchTo; req.session.user.companyName = targetProfile.company_name; req.session.user.companyId = targetProfile.company_id; req.session.user.activeProfileId = targetProfile.id; // Обновляем is_active_profile в массиве req.session.user.profiles.forEach(p => { p.is_active_profile = (p.client_id === switchTo); }); req.user.activeClientId = switchTo; req.user.clientId = switchTo; } catch (e) { console.warn('[entries] switchProfile failed:', e.message); // Локальный fallback без IAM req.session.user.activeClientId = switchTo; if (req.session.user.clientId) req.session.user.clientId = switchTo; req.user.activeClientId = switchTo; } } } else if (!req.user.isAdmin && switchTo && req.user.allClientIds && req.user.allClientIds.includes(switchTo)) { // Fallback: без profiles (mock-режим или IAM не ответил) req.session.user.activeClientId = switchTo; if (req.session.user.clientId) req.session.user.clientId = switchTo; req.user.activeClientId = switchTo; } try { let entries = [], limit = 15, selectedCompany = null, companies = []; if (req.user.adminMode) { const cr = await api.get('/api/v1/companies', token); companies = cr.data.companies || []; const companyId = req.query.company ? parseInt(req.query.company, 10) : null; if (!companyId) { // Админ без ?company= — показываем свою компанию let ownCo = companies.find(c => c.client_id === req.user.clientId) || null; if (!ownCo) { await api.get('/api/v1/entries', token); // создаст компанию через getOrCreateCompany const cr2 = await api.get('/api/v1/companies', token); ownCo = (cr2.data.companies || []).find(c => c.client_id === req.user.clientId) || null; } if (ownCo) { const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : ''; const er = await api.get('/api/v1/entries?company=' + ownCo.id + includeDeleted, token); entries = er.data.entries || []; limit = er.data.limit || 15; selectedCompany = ownCo; } } else { selectedCompany = companies.find(c => c.id === companyId) || null; if (selectedCompany) { const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : ''; const er = await api.get('/api/v1/entries?company=' + companyId + includeDeleted, token); entries = er.data.entries || []; limit = er.data.limit || 15; } } } else { // Обычный пользователь: запрашиваем записи для активной компании const activeClientId = req.user.activeClientId || req.user.clientId; const clientIdParam = req.user.allClientIds && req.user.allClientIds.length > 1 ? '?client_id=' + encodeURIComponent(activeClientId) : ''; const er = await api.get('/api/v1/entries' + clientIdParam, token); entries = er.data.entries || []; limit = er.data.limit || 15; } const hasMultiple = !req.user.isAdmin && req.user.allClientIds && req.user.allClientIds.length > 1; const profiles = req.user.profiles || []; res.render('index', { entries, limit, used: entries.length, user: req.user, isAdmin: req.user.isAdmin, adminMode: req.user.adminMode, companies, selectedCompany, allClientIds: hasMultiple ? req.user.allClientIds : null, activeClientId: req.user.activeClientId || req.user.clientId, profiles, // из IAM — для UI переключателя error: req.query.error || null, success: req.query.success || null, lastValue: req.query.lastValue || '', lastComment: req.query.lastComment || '', includeDeleted: req.query.includeDeleted === 'true', csrfToken: '', }); } catch (e) { res.render('index', { entries: [], limit: 15, used: 0, user: req.user, isAdmin: req.user.isAdmin, adminMode: req.user.adminMode, companies: [], selectedCompany: null, allClientIds: null, activeClientId: null, error: 'Ошибка загрузки: ' + e.message, success: null, lastValue: '', lastComment: '', includeDeleted: false, csrfToken: '', }); } }); // POST /add — создать запись router.post('/add', async (req, res) => { const token = api.token(req); const { value, comment } = req.body; const cq = companyQuery(req); const back = backUrl(req); try { const r = await api.post('/api/v1/entries' + cq, token, { value, comment }); if (r.status === 201) { const msg = r.data.wasNormalized ? 'Запись добавлена (CIDR нормализован до ' + r.data.entry.value_cidr + ')' : 'Запись добавлена'; return res.redirect(back + (back.includes('?') ? '&' : '?') + 'success=' + encodeURIComponent(msg)); } // Ошибка — возвращаем введённые значения чтобы не перепечатывать const err = (r.data && r.data.error) || 'Ошибка'; res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(err) + '&lastValue=' + encodeURIComponent(value || '') + '&lastComment=' + encodeURIComponent(comment || '')); } catch (e) { res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(e.message)); } }); // POST /edit/:id — обновить запись router.post('/edit/:id', async (req, res) => { const token = api.token(req); const { value, comment } = req.body; const cq = companyQuery(req); const back = backUrl(req); try { const r = await api.patch('/api/v1/entries/' + req.params.id + cq, token, { value, comment }); if (r.status === 200) { return res.redirect(back + (back.includes('?') ? '&' : '?') + 'success=' + encodeURIComponent('Запись обновлена')); } // Ошибка — возвращаем введённые значения в модалку const err = (r.data && r.data.error) || 'Ошибка'; res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(err) + '&lastValue=' + encodeURIComponent(value || '') + '&lastComment=' + encodeURIComponent(comment || '')); } catch (e) { res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(e.message)); } }); // POST /delete/:id — удалить запись router.post('/delete/:id', async (req, res) => { const token = api.token(req); const cq = companyQuery(req); const back = backUrl(req); try { const r = await api.delete('/api/v1/entries/' + req.params.id + cq, token); if (r.status === 204) { return res.redirect(back + (back.includes('?') ? '&' : '?') + 'success=' + encodeURIComponent('Запись удалена')); } res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent((r.data && r.data.error) || 'Ошибка')); } catch (e) { res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(e.message)); } }); return router; } module.exports = { createRouter };