v0.5.2: multi-company, isAdmin fix, appVersion, docs
This commit is contained in:
+14
-5
@@ -78,6 +78,9 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, MOCK_USER
|
||||
companyName: user.companyName,
|
||||
isAdmin: user.role === 'admin',
|
||||
};
|
||||
// Добавляем allClientIds для поддержки мульти-компании
|
||||
userData.allClientIds = user.clientId.split(',').map(s => s.trim()).filter(Boolean);
|
||||
userData.activeClientId = userData.allClientIds[0] || user.clientId;
|
||||
|
||||
// session.regenerate() меняет идентификатор сессии — защита от session fixation (A07).
|
||||
// Данные пишем уже в новую сессию.
|
||||
@@ -167,12 +170,15 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, MOCK_USER
|
||||
if (!mockUser) {
|
||||
return res.redirect('/dev-login?error=' + encodeURIComponent('Пользователь не найден'));
|
||||
}
|
||||
const allClientIds = mockUser.clientId.split(',').map(s => s.trim()).filter(Boolean);
|
||||
user = {
|
||||
email: mockUser.email,
|
||||
clientId: mockUser.clientId,
|
||||
companyId: mockUser.companyId,
|
||||
companyName: mockUser.companyName,
|
||||
isAdmin: mockUser.role === 'admin',
|
||||
email: mockUser.email,
|
||||
clientId: mockUser.clientId,
|
||||
allClientIds,
|
||||
activeClientId: allClientIds[0] || mockUser.clientId,
|
||||
companyId: mockUser.companyId,
|
||||
companyName: mockUser.companyName,
|
||||
isAdmin: mockUser.role === 'admin',
|
||||
};
|
||||
} else {
|
||||
// Произвольные поля
|
||||
@@ -180,9 +186,12 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, MOCK_USER
|
||||
if (!clientId) {
|
||||
return res.redirect('/dev-login?error=' + encodeURIComponent('ClientID обязателен'));
|
||||
}
|
||||
const allClientIds = clientId.split(',').map(s => s.trim()).filter(Boolean);
|
||||
user = {
|
||||
email: (req.body.email || '').trim() || clientId + '@dev.local',
|
||||
clientId,
|
||||
allClientIds,
|
||||
activeClientId: allClientIds[0] || clientId,
|
||||
companyId: (req.body.companyId || '').trim() || null,
|
||||
companyName: (req.body.companyName || '').trim() || clientId,
|
||||
isAdmin: req.body.isAdmin === 'on',
|
||||
|
||||
+39
-27
@@ -36,25 +36,32 @@ function createRouter({ q, doubleCsrfProtection, mutationLimiter, generateCsrfTo
|
||||
// Admin: видит все компании + переключатель ?company=<числовой PK>.
|
||||
// User: только своя компания.
|
||||
router.get('/', async (req, res) => {
|
||||
const { clientId, companyName, isAdmin } = req.user;
|
||||
const { clientId, companyName, isAdmin, allClientIds, activeClientId } = req.user;
|
||||
|
||||
// ── Переключатель компании для обычного пользователя ─────────────────
|
||||
// ?switchTo=WZ02001 — меняет активную компанию в сессии (только если есть в allClientIds)
|
||||
const switchTo = (req.query.switchTo || '').trim();
|
||||
let effectiveClientId = clientId;
|
||||
let effectiveCompanyName = companyName;
|
||||
|
||||
if (!isAdmin && switchTo && allClientIds && allClientIds.includes(switchTo)) {
|
||||
effectiveClientId = switchTo;
|
||||
effectiveCompanyName = switchTo; // companyName обновится из БД через getOrCreateCompany
|
||||
req.session.user.clientId = switchTo;
|
||||
req.session.user.activeClientId = switchTo;
|
||||
}
|
||||
|
||||
// Генерируем CSRF-токен здесь, один раз для всей страницы.
|
||||
// Он попадёт в три формы: /add, /edit (modal), /delete (каждая строка).
|
||||
const csrfToken = generateCsrfToken(req, res);
|
||||
|
||||
try {
|
||||
if (isAdmin) {
|
||||
// ── Ветка admin ───────────────────────────────────────────────────────
|
||||
// Загружаем все компании (с active_count через LEFT JOIN)
|
||||
const companies = await q.getAllCompanies();
|
||||
|
||||
// ?company=X — числовой PK выбранной компании.
|
||||
// Если параметра нет — берём первую компанию из списка.
|
||||
const selectedId = req.query.company
|
||||
? parseInt(req.query.company, 10)
|
||||
: (companies[0] ? companies[0].id : null);
|
||||
|
||||
// Ищем объект компании по id. Если id не найден — берём первую.
|
||||
const selectedCompany = companies.find(c => c.id === selectedId) || companies[0] || null;
|
||||
|
||||
const entries = selectedCompany ? await q.listEntries(selectedCompany.id) : [];
|
||||
@@ -63,46 +70,51 @@ function createRouter({ q, doubleCsrfProtection, mutationLimiter, generateCsrfTo
|
||||
return res.render('index', {
|
||||
entries,
|
||||
limit,
|
||||
used: entries.length,
|
||||
user: req.user,
|
||||
isAdmin: true,
|
||||
companies, // для dropdown-переключателя
|
||||
selectedCompany, // текущая выбранная компания
|
||||
error: req.query.error || null,
|
||||
message: req.query.message || null,
|
||||
wasNormalized: req.query.wasNormalized === '1',
|
||||
used: entries.length,
|
||||
user: req.user,
|
||||
isAdmin: true,
|
||||
companies, // dropdown-переключатель всех компаний в БД
|
||||
selectedCompany,
|
||||
allClientIds: null, // admin использует companies (не clientIds)
|
||||
activeClientId: null,
|
||||
error: req.query.error || null,
|
||||
message: req.query.message || null,
|
||||
wasNormalized: req.query.wasNormalized === '1',
|
||||
csrfToken,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Ветка обычного пользователя ────────────────────────────────────────
|
||||
// getOrCreateCompany: создаёт запись в companies если её ещё нет.
|
||||
// clientId и companyName берутся из JWT (нельзя подменить через форму).
|
||||
const company = await q.getOrCreateCompany(clientId, companyName);
|
||||
const company = await q.getOrCreateCompany(effectiveClientId, effectiveCompanyName);
|
||||
const limit = await q.getLimit(company);
|
||||
const entries = await q.listEntries(company.id);
|
||||
|
||||
// Если у пользователя несколько компаний — готовим список для переключателя
|
||||
const hasMultiple = allClientIds && allClientIds.length > 1;
|
||||
|
||||
res.render('index', {
|
||||
entries,
|
||||
limit,
|
||||
used: entries.length,
|
||||
user: req.user,
|
||||
isAdmin: false,
|
||||
companies: null, // пользователь не видит список компаний
|
||||
selectedCompany: company, // его единственная компания
|
||||
error: req.query.error || null,
|
||||
message: req.query.message || null,
|
||||
wasNormalized: req.query.wasNormalized === '1',
|
||||
used: entries.length,
|
||||
user: req.user,
|
||||
isAdmin: false,
|
||||
companies: null,
|
||||
selectedCompany: company,
|
||||
allClientIds: hasMultiple ? allClientIds : null,
|
||||
activeClientId: effectiveClientId,
|
||||
error: req.query.error || null,
|
||||
message: req.query.message || null,
|
||||
wasNormalized: req.query.wasNormalized === '1',
|
||||
csrfToken,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error('GET / error:', e);
|
||||
// При ошибке рендерим страницу с сообщением, не падаем с 500
|
||||
res.render('index', {
|
||||
entries: [], limit: 15, used: 0,
|
||||
user: req.user, isAdmin,
|
||||
companies: null, selectedCompany: null,
|
||||
allClientIds: null, activeClientId: null,
|
||||
error: 'Ошибка загрузки данных: ' + e.message,
|
||||
message: null,
|
||||
wasNormalized: false,
|
||||
|
||||
Reference in New Issue
Block a user