// api2-groups.js // API для управления внешними идентификаторами пользователей const express = require('express'); const router = express.Router(); module.exports = function(app, db) { // Middleware для проверки аутентификации const requireAuth = (req, res, next) => { if (!req.session || !req.session.user) { return res.status(401).json({ error: 'Требуется аутентификация' }); } next(); }; // Middleware для проверки прав администратора const requireAdmin = (req, res, next) => { if (!req.session || !req.session.user || req.session.user.role !== 'admin') { return res.status(403).json({ error: 'Недостаточно прав' }); } next(); }; // 1. Создание таблиц при инициализации function createIdTables() { return new Promise((resolve, reject) => { // Таблица с группами для идентификаторов db.run(` CREATE TABLE IF NOT EXISTS idgroups ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, description TEXT, service_type TEXT NOT NULL, -- 'sberbank', 'yandex', 'ldap', 'other' is_active BOOLEAN DEFAULT true, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `, (err) => { if (err) { console.error('❌ Ошибка создания таблицы idgroups:', err.message); reject(err); return; } // Таблица с идентификаторами пользователей db.run(` CREATE TABLE IF NOT EXISTS idusers ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, service_type TEXT NOT NULL, -- 'sberbank', 'yandex', 'ldap', 'other' external_id TEXT NOT NULL, login TEXT, -- Логин LDAP (если есть) ldap_group TEXT, -- Группа LDAP (если есть) group_id INTEGER, -- Ссылка на группу в idgroups metadata TEXT, -- Дополнительные данные в JSON is_active BOOLEAN DEFAULT true, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, FOREIGN KEY (group_id) REFERENCES idgroups (id) ON DELETE SET NULL, UNIQUE(user_id, service_type, external_id) ) `, (err) => { if (err) { console.error('❌ Ошибка создания таблицы idusers:', err.message); reject(err); return; } // Создаем индексы db.run('CREATE INDEX IF NOT EXISTS idx_idusers_user_id ON idusers(user_id)', (err) => { if (err) console.error('❌ Ошибка создания индекса idx_idusers_user_id:', err.message); }); db.run('CREATE INDEX IF NOT EXISTS idx_idusers_service_type ON idusers(service_type)', (err) => { if (err) console.error('❌ Ошибка создания индекса idx_idusers_service_type:', err.message); }); db.run('CREATE INDEX IF NOT EXISTS idx_idusers_external_id ON idusers(external_id)', (err) => { if (err) console.error('❌ Ошибка создания индекса idx_idusers_external_id:', err.message); }); db.run('CREATE INDEX IF NOT EXISTS idx_idgroups_service_type ON idgroups(service_type)', (err) => { if (err) console.error('❌ Ошибка создания индекса idx_idgroups_service_type:', err.message); }); console.log('✅ Таблицы для внешних идентификаторов созданы'); resolve(); }); }); }); } // 2. Добавление стандартных групп function addDefaultGroups() { const defaultGroups = [ { name: 'Сбербанк - Сотрудники', description: 'Идентификаторы сотрудников в системе Сбербанка', service_type: 'sberbank', is_active: true }, { name: 'Сбербанк - Контрагенты', description: 'Идентификаторы контрагентов в системе Сбербанка', service_type: 'sberbank', is_active: true }, { name: 'Яндекс - Работники', description: 'Идентификаторы работников в системе Яндекса', service_type: 'yandex', is_active: true }, { name: 'Яндекс - Партнеры', description: 'Идентификаторы партнеров в системе Яндекса', service_type: 'yandex', is_active: true }, { name: 'LDAP - Преподаватели', description: 'Пользователи из LDAP с ролью преподавателя', service_type: 'ldap', is_active: true }, { name: 'LDAP - Администрация', description: 'Пользователи из LDAP с ролью администрации', service_type: 'ldap', is_active: true }, { name: 'Прочие системы', description: 'Идентификаторы из других систем', service_type: 'other', is_active: true } ]; defaultGroups.forEach(group => { db.get("SELECT id FROM idgroups WHERE name = ?", [group.name], (err, existing) => { if (err) { console.error(`❌ Ошибка проверки группы ${group.name}:`, err.message); return; } if (!existing) { db.run( `INSERT INTO idgroups (name, description, service_type, is_active) VALUES (?, ?, ?, ?)`, [group.name, group.description, group.service_type, group.is_active ? 1 : 0], (insertErr) => { if (insertErr) { console.error(`❌ Ошибка создания группы ${group.name}:`, insertErr.message); } else { console.log(`✅ Группа "${group.name}" создана по умолчанию`); } } ); } }); }); } // 3. API эндпоинты // GET /api2/groups - Получить все группы (доступно всем аутентифицированным) router.get('/api2/groups', requireAuth, (req, res) => { const { service_type, is_active } = req.query; let query = 'SELECT * FROM idgroups WHERE 1=1'; const params = []; if (service_type) { query += ' AND service_type = ?'; params.push(service_type); } if (is_active !== undefined) { query += ' AND is_active = ?'; params.push(is_active === 'true' ? 1 : 0); } query += ' ORDER BY service_type, name'; db.all(query, params, (err, groups) => { if (err) { console.error('❌ Ошибка получения групп:', err); return res.status(500).json({ error: 'Ошибка получения групп' }); } res.json(groups || []); }); }); // GET /api2/groups/:id - Получить группу по ID (доступно всем аутентифицированным) router.get('/api2/groups/:id', requireAuth, (req, res) => { const { id } = req.params; db.get('SELECT * FROM idgroups WHERE id = ?', [id], (err, group) => { if (err) { console.error('❌ Ошибка получения группы:', err); return res.status(500).json({ error: 'Ошибка получения группы' }); } if (!group) { return res.status(404).json({ error: 'Группа не найдена' }); } res.json(group); }); }); // POST /api2/groups - Создать новую группу (только админ) router.post('/api2/groups', requireAuth, requireAdmin, (req, res) => { const { name, description, service_type, is_active } = req.body; if (!name || !service_type) { return res.status(400).json({ error: 'Обязательные поля: name, service_type' }); } const validServiceTypes = ['sberbank', 'yandex', 'ldap', 'other']; if (!validServiceTypes.includes(service_type)) { return res.status(400).json({ error: `Недопустимый тип сервиса. Допустимые значения: ${validServiceTypes.join(', ')}` }); } db.run( `INSERT INTO idgroups (name, description, service_type, is_active) VALUES (?, ?, ?, ?)`, [ name, description || '', service_type, is_active !== undefined ? (is_active ? 1 : 0) : 1 ], function(err) { if (err) { if (err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ error: 'Группа с таким именем уже существует' }); } console.error('❌ Ошибка создания группы:', err); return res.status(500).json({ error: 'Ошибка создания группы' }); } res.status(201).json({ success: true, id: this.lastID, message: 'Группа успешно создана' }); } ); }); // PUT /api2/groups/:id - Обновить группу (только админ) router.put('/api2/groups/:id', requireAuth, requireAdmin, (req, res) => { const { id } = req.params; const { name, description, service_type, is_active } = req.body; // Проверяем существование группы db.get('SELECT id FROM idgroups WHERE id = ?', [id], (err, existing) => { if (err) { console.error('❌ Ошибка проверки группы:', err); return res.status(500).json({ error: 'Ошибка проверки группы' }); } if (!existing) { return res.status(404).json({ error: 'Группа не найдена' }); } // Собираем поля для обновления const updates = []; const params = []; if (name !== undefined) { updates.push('name = ?'); params.push(name); } if (description !== undefined) { updates.push('description = ?'); params.push(description); } if (service_type !== undefined) { const validServiceTypes = ['sberbank', 'yandex', 'ldap', 'other']; if (!validServiceTypes.includes(service_type)) { return res.status(400).json({ error: `Недопустимый тип сервиса. Допустимые значения: ${validServiceTypes.join(', ')}` }); } updates.push('service_type = ?'); params.push(service_type); } if (is_active !== undefined) { updates.push('is_active = ?'); params.push(is_active ? 1 : 0); } if (updates.length === 0) { return res.status(400).json({ error: 'Нет данных для обновления' }); } updates.push('updated_at = CURRENT_TIMESTAMP'); params.push(id); const query = `UPDATE idgroups SET ${updates.join(', ')} WHERE id = ?`; db.run(query, params, function(err) { if (err) { if (err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ error: 'Группа с таким именем уже существует' }); } console.error('❌ Ошибка обновления группы:', err); return res.status(500).json({ error: 'Ошибка обновления группы' }); } res.json({ success: true, changes: this.changes, message: 'Группа успешно обновлена' }); }); }); }); // DELETE /api2/groups/:id - Удалить группу (только админ) router.delete('/api2/groups/:id', requireAuth, requireAdmin, (req, res) => { const { id } = req.params; // Проверяем, используется ли группа db.get('SELECT COUNT(*) as count FROM idusers WHERE group_id = ?', [id], (err, result) => { if (err) { console.error('❌ Ошибка проверки использования группы:', err); return res.status(500).json({ error: 'Ошибка проверки группы' }); } if (result.count > 0) { return res.status(400).json({ error: 'Невозможно удалить группу, так как она используется в идентификаторах пользователей', used_count: result.count }); } db.run('DELETE FROM idgroups WHERE id = ?', [id], function(err) { if (err) { console.error('❌ Ошибка удаления группы:', err); return res.status(500).json({ error: 'Ошибка удаления группы' }); } res.json({ success: true, changes: this.changes, message: 'Группа успешно удалена' }); }); }); }); // GET /api2/idusers - Получить все идентификаторы пользователей (доступно всем аутентифицированным) router.get('/api2/idusers', requireAuth, (req, res) => { const { user_id, service_type, external_id, group_id, is_active } = req.query; let query = ` SELECT iu.*, u.name as user_name, u.login as user_login, u.email as user_email, g.name as group_name FROM idusers iu LEFT JOIN users u ON iu.user_id = u.id LEFT JOIN idgroups g ON iu.group_id = g.id WHERE 1=1 `; const params = []; if (user_id) { query += ' AND iu.user_id = ?'; params.push(user_id); } if (service_type) { query += ' AND iu.service_type = ?'; params.push(service_type); } if (external_id) { query += ' AND iu.external_id LIKE ?'; params.push(`%${external_id}%`); } if (group_id) { query += ' AND iu.group_id = ?'; params.push(group_id); } if (is_active !== undefined) { query += ' AND iu.is_active = ?'; params.push(is_active === 'true' ? 1 : 0); } query += ' ORDER BY iu.service_type, iu.external_id'; db.all(query, params, (err, idusers) => { if (err) { console.error('❌ Ошибка получения идентификаторов пользователей:', err); return res.status(500).json({ error: 'Ошибка получения данных' }); } // Парсим JSON metadata если есть const result = (idusers || []).map(item => { if (item.metadata) { try { item.metadata = JSON.parse(item.metadata); } catch (e) { item.metadata = {}; } } else { item.metadata = {}; } return item; }); res.json(result); }); }); // GET /api2/idusers/:id - Получить идентификатор по ID (доступно всем аутентифицированным) router.get('/api2/idusers/:id', requireAuth, (req, res) => { const { id } = req.params; db.get(` SELECT iu.*, u.name as user_name, u.login as user_login, u.email as user_email, g.name as group_name FROM idusers iu LEFT JOIN users u ON iu.user_id = u.id LEFT JOIN idgroups g ON iu.group_id = g.id WHERE iu.id = ? `, [id], (err, iduser) => { if (err) { console.error('❌ Ошибка получения идентификатора:', err); return res.status(500).json({ error: 'Ошибка получения данных' }); } if (!iduser) { return res.status(404).json({ error: 'Идентификатор не найден' }); } // Парсим JSON metadata если есть if (iduser.metadata) { try { iduser.metadata = JSON.parse(iduser.metadata); } catch (e) { iduser.metadata = {}; } } else { iduser.metadata = {}; } res.json(iduser); }); }); // GET /api2/idusers/user/:userId - Получить идентификаторы конкретного пользователя (доступно всем аутентифицированным) router.get('/api2/idusers/user/:userId', requireAuth, (req, res) => { const { userId } = req.params; const { service_type } = req.query; let query = ` SELECT iu.*, g.name as group_name FROM idusers iu LEFT JOIN idgroups g ON iu.group_id = g.id WHERE iu.user_id = ? `; const params = [userId]; if (service_type) { query += ' AND iu.service_type = ?'; params.push(service_type); } query += ' ORDER BY iu.service_type, iu.external_id'; db.all(query, params, (err, idusers) => { if (err) { console.error('❌ Ошибка получения идентификаторов пользователя:', err); return res.status(500).json({ error: 'Ошибка получения данных' }); } // Парсим JSON metadata если есть const result = (idusers || []).map(item => { if (item.metadata) { try { item.metadata = JSON.parse(item.metadata); } catch (e) { item.metadata = {}; } } else { item.metadata = {}; } return item; }); res.json(result); }); }); // POST /api2/idusers - Создать новый идентификатор пользователя (только админ) router.post('/api2/idusers', requireAuth, requireAdmin, (req, res) => { const { user_id, service_type, external_id, login, ldap_group, group_id, metadata, is_active } = req.body; // Валидация обязательных полей if (!user_id || !service_type || !external_id) { return res.status(400).json({ error: 'Обязательные поля: user_id, service_type, external_id' }); } const validServiceTypes = ['sberbank', 'yandex', 'ldap', 'other']; if (!validServiceTypes.includes(service_type)) { return res.status(400).json({ error: `Недопустимый тип сервиса. Допустимые значения: ${validServiceTypes.join(', ')}` }); } // Проверяем существование пользователя db.get('SELECT id FROM users WHERE id = ?', [user_id], (err, user) => { if (err) { console.error('❌ Ошибка проверки пользователя:', err); return res.status(500).json({ error: 'Ошибка проверки пользователя' }); } if (!user) { return res.status(404).json({ error: 'Пользователь не найден' }); } // Если указана группа, проверяем ее существование if (group_id) { db.get('SELECT id, service_type FROM idgroups WHERE id = ?', [group_id], (err, group) => { if (err) { console.error('❌ Ошибка проверки группы:', err); return res.status(500).json({ error: 'Ошибка проверки группы' }); } if (!group) { return res.status(404).json({ error: 'Указанная группа не найдена' }); } // Проверяем соответствие типа сервиса if (group.service_type !== service_type) { return res.status(400).json({ error: `Тип сервиса группы (${group.service_type}) не соответствует типу сервиса идентификатора (${service_type})` }); } // Создаем идентификатор createIdUser(); }); } else { // Создаем идентификатор без группы createIdUser(); } function createIdUser() { const metadataJson = metadata ? JSON.stringify(metadata) : null; db.run( `INSERT INTO idusers (user_id, service_type, external_id, login, ldap_group, group_id, metadata, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ user_id, service_type, external_id, login || null, ldap_group || null, group_id || null, metadataJson, is_active !== undefined ? (is_active ? 1 : 0) : 1 ], function(err) { if (err) { if (err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ error: 'Идентификатор с такими параметрами уже существует для этого пользователя' }); } console.error('❌ Ошибка создания идентификатора:', err); return res.status(500).json({ error: 'Ошибка создания идентификатора' }); } res.status(201).json({ success: true, id: this.lastID, message: 'Идентификатор успешно создан' }); } ); } }); }); // PUT /api2/idusers/:id - Обновить идентификатор пользователя (только админ) router.put('/api2/idusers/:id', requireAuth, requireAdmin, (req, res) => { const { id } = req.params; const { user_id, service_type, external_id, login, ldap_group, group_id, metadata, is_active } = req.body; // Проверяем существование идентификатора db.get('SELECT * FROM idusers WHERE id = ?', [id], (err, existing) => { if (err) { console.error('❌ Ошибка проверки идентификатора:', err); return res.status(500).json({ error: 'Ошибка проверки идентификатора' }); } if (!existing) { return res.status(404).json({ error: 'Идентификатор не найден' }); } // Собираем поля для обновления const updates = []; const params = []; if (user_id !== undefined) { updates.push('user_id = ?'); params.push(user_id); } if (service_type !== undefined) { const validServiceTypes = ['sberbank', 'yandex', 'ldap', 'other']; if (!validServiceTypes.includes(service_type)) { return res.status(400).json({ error: `Недопустимый тип сервиса. Допустимые значения: ${validServiceTypes.join(', ')}` }); } updates.push('service_type = ?'); params.push(service_type); } if (external_id !== undefined) { updates.push('external_id = ?'); params.push(external_id); } if (login !== undefined) { updates.push('login = ?'); params.push(login || null); } if (ldap_group !== undefined) { updates.push('ldap_group = ?'); params.push(ldap_group || null); } if (group_id !== undefined) { updates.push('group_id = ?'); params.push(group_id || null); } if (metadata !== undefined) { updates.push('metadata = ?'); params.push(metadata ? JSON.stringify(metadata) : null); } if (is_active !== undefined) { updates.push('is_active = ?'); params.push(is_active ? 1 : 0); } if (updates.length === 0) { return res.status(400).json({ error: 'Нет данных для обновления' }); } updates.push('updated_at = CURRENT_TIMESTAMP'); params.push(id); const query = `UPDATE idusers SET ${updates.join(', ')} WHERE id = ?`; db.run(query, params, function(err) { if (err) { if (err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ error: 'Идентификатор с такими параметрами уже существует' }); } console.error('❌ Ошибка обновления идентификатора:', err); return res.status(500).json({ error: 'Ошибка обновления идентификатора' }); } res.json({ success: true, changes: this.changes, message: 'Идентификатор успешно обновлен' }); }); }); }); // DELETE /api2/idusers/:id - Удалить идентификатор пользователя (только админ) router.delete('/api2/idusers/:id', requireAuth, requireAdmin, (req, res) => { const { id } = req.params; db.run('DELETE FROM idusers WHERE id = ?', [id], function(err) { if (err) { console.error('❌ Ошибка удаления идентификатора:', err); return res.status(500).json({ error: 'Ошибка удаления идентификатора' }); } res.json({ success: true, changes: this.changes, message: 'Идентификатор успешно удален' }); }); }); // GET /api2/idusers/search - Поиск идентификаторов (доступно всем аутентифицированным) router.get('/api2/idusers/search', requireAuth, (req, res) => { const { q, service_type } = req.query; if (!q || q.length < 2) { return res.status(400).json({ error: 'Строка поиска должна содержать минимум 2 символа' }); } let query = ` SELECT iu.*, u.name as user_name, u.login as user_login, u.email as user_email, g.name as group_name FROM idusers iu LEFT JOIN users u ON iu.user_id = u.id LEFT JOIN idgroups g ON iu.group_id = g.id WHERE (iu.external_id LIKE ? OR iu.login LIKE ? OR u.name LIKE ? OR u.login LIKE ?) `; const params = [`%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`]; if (service_type) { query += ' AND iu.service_type = ?'; params.push(service_type); } query += ' ORDER BY iu.service_type, iu.external_id LIMIT 50'; db.all(query, params, (err, idusers) => { if (err) { console.error('❌ Ошибка поиска идентификаторов:', err); return res.status(500).json({ error: 'Ошибка поиска' }); } // Парсим JSON metadata если есть const result = (idusers || []).map(item => { if (item.metadata) { try { item.metadata = JSON.parse(item.metadata); } catch (e) { item.metadata = {}; } } else { item.metadata = {}; } return item; }); res.json(result); }); }); // GET /api2/idusers/stats - Статистика по идентификаторам (доступно всем аутентифицированным) router.get('/api2/idusers/stats', requireAuth, (req, res) => { // Получаем статистику по типам сервисов const query = ` SELECT service_type, COUNT(*) as total_count, COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_count, COUNT(DISTINCT user_id) as unique_users FROM idusers GROUP BY service_type ORDER BY total_count DESC `; db.all(query, [], (err, stats) => { if (err) { console.error('❌ Ошибка получения статистики:', err); return res.status(500).json({ error: 'Ошибка получения статистики' }); } // Получаем общую статистику db.get(` SELECT COUNT(*) as total_identifiers, COUNT(DISTINCT user_id) as total_users, COUNT(CASE WHEN is_active = 1 THEN 1 END) as total_active FROM idusers `, [], (err, totalStats) => { if (err) { console.error('❌ Ошибка получения общей статистики:', err); return res.status(500).json({ error: 'Ошибка получения статистики' }); } res.json({ by_service_type: stats || [], totals: totalStats || {}, timestamp: new Date().toISOString() }); }); }); }); // 4. Инициализация при подключении модуля const init = async () => { try { await createIdTables(); addDefaultGroups(); console.log('✅ API для внешних идентификаторов инициализирован'); } catch (error) { console.error('❌ Ошибка инициализации API для внешних идентификаторов:', error.message); } }; // Запускаем инициализацию init(); // Подключаем роутер к приложению app.use(router); // Экспортируем функции для использования в других модулях return { // Функция для получения внешнего идентификатора пользователя getUserIdByExternalId: function(service_type, external_id, callback) { db.get( 'SELECT user_id FROM idusers WHERE service_type = ? AND external_id = ? AND is_active = 1', [service_type, external_id], (err, result) => { if (err) { console.error('❌ Ошибка получения идентификатора пользователя:', err); return callback(err, null); } callback(null, result ? result.user_id : null); } ); }, // Функция для получения внешних идентификаторов пользователя getUserExternalIds: function(user_id, service_type, callback) { let query = 'SELECT * FROM idusers WHERE user_id = ? AND is_active = 1'; const params = [user_id]; if (service_type) { query += ' AND service_type = ?'; params.push(service_type); } db.all(query, params, (err, results) => { if (err) { console.error('❌ Ошибка получения внешних идентификаторов:', err); return callback(err, null); } // Парсим JSON metadata если есть const formattedResults = (results || []).map(item => { if (item.metadata) { try { item.metadata = JSON.parse(item.metadata); } catch (e) { item.metadata = {}; } } else { item.metadata = {}; } return item; }); callback(null, formattedResults); }); }, // Функция для добавления или обновления идентификатора upsertUserId: function(user_id, service_type, external_id, data, callback) { const { login, ldap_group, group_id, metadata, is_active } = data || {}; db.get( 'SELECT id FROM idusers WHERE user_id = ? AND service_type = ? AND external_id = ?', [user_id, service_type, external_id], (err, existing) => { if (err) { return callback(err); } const metadataJson = metadata ? JSON.stringify(metadata) : null; if (existing) { // Обновляем существующий db.run( `UPDATE idusers SET login = COALESCE(?, login), ldap_group = COALESCE(?, ldap_group), group_id = COALESCE(?, group_id), metadata = COALESCE(?, metadata), is_active = COALESCE(?, is_active), updated_at = CURRENT_TIMESTAMP WHERE id = ?`, [ login || null, ldap_group || null, group_id || null, metadataJson, is_active !== undefined ? (is_active ? 1 : 0) : 1, existing.id ], function(err) { callback(err, { updated: true, id: existing.id }); } ); } else { // Создаем новый db.run( `INSERT INTO idusers (user_id, service_type, external_id, login, ldap_group, group_id, metadata, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ user_id, service_type, external_id, login || null, ldap_group || null, group_id || null, metadataJson, is_active !== undefined ? (is_active ? 1 : 0) : 1 ], function(err) { callback(err, { created: true, id: this.lastID }); } ); } } ); } }; };