From 4f33ef782fb9fc3793c4ab01abcc13f8acb1f54c Mon Sep 17 00:00:00 2001 From: kalugin66 Date: Sat, 7 Feb 2026 14:21:10 +0500 Subject: [PATCH] clear2 --- api2-groups.js | 971 +++++++++++++++++++++++++++++++++++++++ public/doc-auth.js | 158 ------- public/doc-tasks.js | 558 ----------------------- public/doc-users.js | 260 ----------- public/doc.html | 1058 ------------------------------------------- public/documents.js | 646 -------------------------- server.js | 8 +- 7 files changed, 978 insertions(+), 2681 deletions(-) create mode 100644 api2-groups.js delete mode 100644 public/doc-auth.js delete mode 100644 public/doc-tasks.js delete mode 100644 public/doc-users.js delete mode 100644 public/doc.html delete mode 100644 public/documents.js diff --git a/api2-groups.js b/api2-groups.js new file mode 100644 index 0000000..045f614 --- /dev/null +++ b/api2-groups.js @@ -0,0 +1,971 @@ +// 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 }); + } + ); + } + } + ); + } + }; +}; \ No newline at end of file diff --git a/public/doc-auth.js b/public/doc-auth.js deleted file mode 100644 index 55e2f67..0000000 --- a/public/doc-auth.js +++ /dev/null @@ -1,158 +0,0 @@ -// auth.js - Аутентификация и авторизация -let currentUser = null; - -async function checkAuth() { - try { - const response = await fetch('/api/user'); - if (response.ok) { - const data = await response.json(); - currentUser = data.user; - showMainInterface(); - } else { - showLoginInterface(); - } - } catch (error) { - showLoginInterface(); - } -} - -function showLoginInterface() { - document.getElementById('login-modal').style.display = 'block'; - document.querySelector('.container').style.display = 'none'; -} - -function showMainInterface() { - document.getElementById('login-modal').style.display = 'none'; - document.querySelector('.container').style.display = 'block'; - - let userInfo = `Вы вошли как: ${currentUser.name}`; - if (currentUser.auth_type === 'ldap') { - userInfo += ` (LDAP)`; - } - - // Показываем только группы, которые влияют на роль - if (currentUser.groups && currentUser.groups.length > 0) { - // Получаем все группы ролей из конфигурации - const roleGroups = []; - - // Администраторы - if (window.ALLOWED_GROUPS) { - roleGroups.push(...window.ALLOWED_GROUPS.split(',').map(g => g.trim())); - } - - // Секретари - if (window.SECRETARY_GROUPS) { - roleGroups.push(...window.SECRETARY_GROUPS.split(',').map(g => g.trim())); - } - - // Группа помощи - if (window.HELP_GROUPS) { - roleGroups.push(...window.HELP_GROUPS.split(',').map(g => g.trim())); - } - - // IT поддержка - if (window.ITHELP_GROUPS) { - roleGroups.push(...window.ITHELP_GROUPS.split(',').map(g => g.trim())); - } - - // Заявки - if (window.REQUEST_GROUPS) { - roleGroups.push(...window.REQUEST_GROUPS.split(',').map(g => g.trim())); - } - - // Задачи - if (window.TASKS_GROUPS) { - roleGroups.push(...window.TASKS_GROUPS.split(',').map(g => g.trim())); - } - - // Фильтруем группы пользователя, оставляя только те, что влияют на роль - const relevantGroups = currentUser.groups.filter(group => - roleGroups.includes(group) - ); - - // Также всегда показываем роль пользователя - if (currentUser.role) { - userInfo += ` | Роль: ${getRoleDisplayName(currentUser.role)}`; - } - - // Показываем группы только если есть релевантные - if (relevantGroups.length > 0) { - userInfo += ` | Группы ролей: ${relevantGroups.join(', ')}`; - } - } - - document.getElementById('current-user').textContent = userInfo; - - document.getElementById('tasks-controls').style.display = 'block'; - - const showDeletedLabel = document.querySelector('.show-deleted-label'); - if (showDeletedLabel) { - if (currentUser.role === 'admin') { - showDeletedLabel.style.display = 'flex'; - } else { - showDeletedLabel.style.display = 'none'; - } - } - - loadUsers(); - loadTasks(); - loadActivityLogs(); - showSection('tasks'); - - showingTasksWithoutDate = false; - const btn = document.getElementById('tasks-no-date-btn'); - if (btn) btn.classList.remove('active'); -} - -// Вспомогательная функция для отображения понятного имени роли -function getRoleDisplayName(role) { - const roleNames = { - 'admin': 'Администратор', - 'secretary': 'Секретарь', - 'help': 'Помощь', - 'ithelp': 'IT поддержка', - 'request': 'Заявки', - 'tasks': 'Адмиинистрация', - 'teacher': 'Учитель' - }; - return roleNames[role] || role; -} - -async function login(event) { - event.preventDefault(); - - const login = document.getElementById('login').value; - const password = document.getElementById('password').value; - - try { - const response = await fetch('/api/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ login, password }) - }); - - if (response.ok) { - const data = await response.json(); - currentUser = data.user; - showMainInterface(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка входа'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка подключения к серверу'); - } -} - -async function logout() { - try { - await fetch('/api/logout', { method: 'POST' }); - currentUser = null; - showLoginInterface(); - } catch (error) { - console.error('Ошибка выхода:', error); - } -} \ No newline at end of file diff --git a/public/doc-tasks.js b/public/doc-tasks.js deleted file mode 100644 index e3bd3fa..0000000 --- a/public/doc-tasks.js +++ /dev/null @@ -1,558 +0,0 @@ -// tasks.js - Основные операции с задачами -let tasks = []; -let expandedTasks = new Set(); -let showingTasksWithoutDate = false; - -async function loadTasks() { - try { - showingTasksWithoutDate = false; - const btn = document.getElementById('tasks-no-date-btn'); - if (btn) btn.classList.remove('active'); - - const search = document.getElementById('search-tasks')?.value || ''; - const statusFilter = document.getElementById('status-filter')?.value || 'active,in_progress,assigned,overdue,rework'; - const creatorFilter = document.getElementById('creator-filter')?.value || ''; - const assigneeFilter = document.getElementById('assignee-filter')?.value || ''; - const deadlineFilter = document.getElementById('deadline-filter')?.value || ''; - const showDeleted = document.getElementById('show-deleted')?.checked || false; - - let url = '/api/tasks?'; - if (search) url += `search=${encodeURIComponent(search)}&`; - if (statusFilter) url += `status=${encodeURIComponent(statusFilter)}&`; - if (creatorFilter) url += `creator=${encodeURIComponent(creatorFilter)}&`; - if (assigneeFilter) url += `assignee=${encodeURIComponent(assigneeFilter)}&`; - if (deadlineFilter) url += `deadline=${encodeURIComponent(deadlineFilter)}&`; - if (showDeleted) url += `showDeleted=true&`; - - const response = await fetch(url); - tasks = await response.json(); - - // Загружаем файлы для всех задач - await Promise.all(tasks.map(async (task) => { - try { - const filesResponse = await fetch(`/api/tasks/${task.id}/files`); - if (filesResponse.ok) { - task.files = await filesResponse.json(); - } else { - task.files = []; - } - } catch (error) { - console.error(`Ошибка загрузки файлов для задачи ${task.id}:`, error); - task.files = []; - } - })); - - renderTasks(); - - } catch (error) { - console.error('Ошибка загрузки задач:', error); - } -} - -function showTasksWithoutDate() { - showingTasksWithoutDate = true; - const btn = document.getElementById('tasks-no-date-btn'); - if (btn) btn.classList.add('active'); - loadTasksWithoutDate(); -} - -async function loadTasksWithoutDate() { - try { - const response = await fetch('/api/tasks'); - if (!response.ok) throw new Error('Ошибка загрузки задач'); - - const allTasks = await response.json(); - tasks = allTasks.filter(task => { - const hasTaskDueDate = !task.due_date; - const hasAssignmentDueDates = task.assignments && - task.assignments.every(assignment => !assignment.due_date); - return hasTaskDueDate && hasAssignmentDueDates; - }); - - // Загружаем файлы для всех задач - await Promise.all(tasks.map(async (task) => { - try { - const filesResponse = await fetch(`/api/tasks/${task.id}/files`); - if (filesResponse.ok) { - task.files = await filesResponse.json(); - } else { - task.files = []; - } - } catch (error) { - console.error(`Ошибка загрузки файлов для задачи ${task.id}:`, error); - task.files = []; - } - })); - - renderTasks(); - } catch (error) { - console.error('Ошибка загрузки задач без срока:', error); - } -} - -async function createTask(event) { - event.preventDefault(); - - if (!currentUser) { - alert('Требуется аутентификация'); - return; - } - - const formData = new FormData(); - formData.append('title', document.getElementById('title').value); - formData.append('description', document.getElementById('description').value); - - const dueDate = document.getElementById('due-date').value; - if (!dueDate) { - alert('Дата и время выполнения обязательны'); - return; - } - formData.append('dueDate', dueDate); - - // Используем selectedUsers вместо прямого доступа к DOM - if (selectedUsers.length === 0) { - alert('Выберите хотя бы одного исполнителя'); - return; - } - selectedUsers.forEach(userId => { - formData.append('assignedUsers', userId); - }); - - const files = document.getElementById('files').files; - for (let i = 0; i < files.length; i++) { - formData.append('files', files[i]); - } - - try { - const response = await fetch('/api/tasks', { - method: 'POST', - body: formData - }); - - if (response.ok) { - alert('Задача успешно создана!'); - document.getElementById('create-task-form').reset(); - document.getElementById('file-list').innerHTML = ''; - document.getElementById('user-search').value = ''; - selectedUsers = []; - renderUsersChecklist(); - loadTasks(); - loadActivityLogs(); - showSection('tasks'); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка создания задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка создания задачи'); - } -} - -async function openEditModal(taskId) { - try { - const response = await fetch(`/api/tasks/${taskId}`); - if (!response.ok) { - if (response.status === 404) { - alert('Задача не найдена или у вас нет прав доступа'); - } - throw new Error('Ошибка загрузки задачи'); - } - - const task = await response.json(); - - if (!canUserEditTask(task)) { - alert('У вас нет прав для редактирования этой задачи'); - return; - } - - document.getElementById('edit-task-id').value = task.id; - document.getElementById('edit-title').value = task.title; - document.getElementById('edit-description').value = task.description || ''; - - document.getElementById('edit-due-date').value = task.due_date ? formatDateTimeForInput(task.due_date) : ''; - - // Устанавливаем выбранных пользователей - editSelectedUsers = task.assignments ? task.assignments.map(a => a.user_id) : []; - renderEditUsersChecklist(users); - - // Показываем существующие файлы - currentEditTaskFiles = task.files || []; - updateEditFileList(); - - document.getElementById('edit-task-modal').style.display = 'block'; - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка загрузки задачи'); - } -} - -function closeEditModal() { - document.getElementById('edit-task-modal').style.display = 'none'; - document.getElementById('edit-file-list').innerHTML = ''; - document.getElementById('edit-user-search').value = ''; - editSelectedUsers = []; - currentEditTaskFiles = []; - filterEditUsers(); -} - -async function updateTask(event) { - event.preventDefault(); - - const taskId = document.getElementById('edit-task-id').value; - const title = document.getElementById('edit-title').value; - const description = document.getElementById('edit-description').value; - const dueDate = document.getElementById('edit-due-date').value; - - if (!dueDate) { - alert('Дата и время выполнения обязательны'); - return; - } - - // Используем editSelectedUsers - const assignedUserIds = editSelectedUsers; - - const formData = new FormData(); - formData.append('title', title); - formData.append('description', description); - formData.append('assignedUsers', JSON.stringify(assignedUserIds)); - formData.append('dueDate', dueDate); - - const files = document.getElementById('edit-files').files; - for (let i = 0; i < files.length; i++) { - formData.append('files', files[i]); - } - - try { - const response = await fetch(`/api/tasks/${taskId}`, { - method: 'PUT', - body: formData - }); - - if (response.ok) { - alert('Задача успешно обновлена!'); - closeEditModal(); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка обновления задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка обновления задачи'); - } -} - -function openCopyModal(taskId) { - document.getElementById('copy-task-id').value = taskId; - - // Устанавливаем дату по умолчанию (через 7 дней) - const defaultDate = new Date(); - defaultDate.setDate(defaultDate.getDate() + 7); - document.getElementById('copy-due-date').value = defaultDate.toISOString().substring(0, 16); - - // Сбрасываем выбранных пользователей - copySelectedUsers = []; - renderCopyUsersChecklist(users); - - document.getElementById('copy-task-modal').style.display = 'block'; -} - -function closeCopyModal() { - document.getElementById('copy-task-modal').style.display = 'none'; - document.getElementById('copy-user-search').value = ''; - copySelectedUsers = []; - filterCopyUsers(); -} - -async function copyTask(event) { - event.preventDefault(); - - const taskId = document.getElementById('copy-task-id').value; - const dueDate = document.getElementById('copy-due-date').value; - - if (!dueDate) { - alert('Дата и время выполнения обязательны для копии задачи'); - return; - } - - // Используем copySelectedUsers - const assignedUserIds = copySelectedUsers; - - if (assignedUserIds.length === 0) { - alert('Выберите хотя бы одного исполнителя для копии задачи'); - return; - } - - try { - const response = await fetch(`/api/tasks/${taskId}/copy`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - assignedUsers: assignedUserIds, - dueDate: dueDate - }) - }); - - if (response.ok) { - alert('Копия задачи успешно создана!'); - closeCopyModal(); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка создания копии задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка создания копии задачи'); - } -} - -async function closeTask(taskId) { - if (!confirm('Вы уверены, что хотите закрыть эту задачу? Исполнители больше не будут видеть её.')) { - return; - } - - try { - const response = await fetch(`/api/tasks/${taskId}/close`, { - method: 'POST' - }); - - if (response.ok) { - alert('Задача закрыта!'); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка закрытия задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка закрытия задачи'); - } -} - -async function reopenTask(taskId) { - try { - const response = await fetch(`/api/tasks/${taskId}/reopen`, { - method: 'POST' - }); - - if (response.ok) { - alert('Задача открыта!'); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка открытия задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка открытия задачи'); - } -} - -async function deleteTask(taskId) { - if (!confirm('Вы уверены, что хотите удалить эту задачу?')) { - return; - } - - try { - const response = await fetch(`/api/tasks/${taskId}`, { - method: 'DELETE' - }); - - if (response.ok) { - alert('Задача удалена!'); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка удаления задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка удаления задачи'); - } -} - -async function restoreTask(taskId) { - try { - const response = await fetch(`/api/tasks/${taskId}/restore`, { - method: 'POST' - }); - - if (response.ok) { - alert('Задача восстановлена!'); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка восстановления задачи'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка восстановления задачи'); - } -} - -function openEditAssignmentModal(taskId, userId) { - const task = tasks.find(t => t.id === taskId); - if (!task) return; - - const assignment = task.assignments.find(a => a.user_id === userId); - if (!assignment) return; - - document.getElementById('edit-assignment-task-id').value = taskId; - document.getElementById('edit-assignment-user-id').value = userId; - document.getElementById('edit-assignment-due-date').value = assignment.due_date ? formatDateTimeForInput(assignment.due_date) : ''; - - document.getElementById('edit-assignment-modal').style.display = 'block'; -} - -function closeEditAssignmentModal() { - document.getElementById('edit-assignment-modal').style.display = 'none'; -} - -async function updateAssignment(event) { - event.preventDefault(); - - const taskId = document.getElementById('edit-assignment-task-id').value; - const userId = document.getElementById('edit-assignment-user-id').value; - const dueDate = document.getElementById('edit-assignment-due-date').value; - - if (!dueDate) { - alert('Дата и время выполнения обязательны'); - return; - } - - try { - const response = await fetch(`/api/tasks/${taskId}/assignment/${userId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - dueDate: dueDate - }) - }); - - if (response.ok) { - alert('Сроки исполнителя обновлены!'); - closeEditAssignmentModal(); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка обновления сроков'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка обновления сроков'); - } -} - -function openReworkModal(taskId) { - document.getElementById('rework-task-id').value = taskId; - document.getElementById('rework-task-modal').style.display = 'block'; -} - -function closeReworkModal() { - document.getElementById('rework-task-modal').style.display = 'none'; - document.getElementById('rework-comment').value = ''; -} - -async function sendForRework(event) { - event.preventDefault(); - - const taskId = document.getElementById('rework-task-id').value; - const comment = document.getElementById('rework-comment').value; - - try { - const response = await fetch(`/api/tasks/${taskId}/rework`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ comment }) - }); - - if (response.ok) { - alert('Задача возвращена на доработку!'); - closeReworkModal(); - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка возврата задачи на доработку'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка возврата задачи на доработку'); - } -} - -async function updateStatus(taskId, userId, status) { - try { - const response = await fetch(`/api/tasks/${taskId}/status`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ userId, status }) - }); - - if (response.ok) { - loadTasks(); - loadActivityLogs(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка обновления статуса'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка обновления статуса'); - } -} - -function canUserEditTask(task) { - if (!currentUser) return false; - - // Администратор может всё - if (currentUser.role === 'admin') return true; - - // Создатель может редактировать свою задачу - if (parseInt(task.created_by) === currentUser.id) { - // Но если задача уже назначена другим пользователям, - // создатель может только просматривать - if (task.assignments && task.assignments.length > 0) { - // Проверяем, назначена ли задача другим пользователям (не только себе) - const assignedToOthers = task.assignments.some(assignment => - parseInt(assignment.user_id) !== currentUser.id - ); - - if (assignedToOthers) { - // Создатель может только просматривать и закрывать задачу - return false; - } - } - return true; - } - - // Исполнитель может менять только свой статус - if (task.assignments) { - const isExecutor = task.assignments.some(assignment => - parseInt(assignment.user_id) === currentUser.id - ); - if (isExecutor) { - // Исполнитель может менять только статус - return false; - } - } - - return false; -} \ No newline at end of file diff --git a/public/doc-users.js b/public/doc-users.js deleted file mode 100644 index 61186da..0000000 --- a/public/doc-users.js +++ /dev/null @@ -1,260 +0,0 @@ -// users.js - Управление пользователями -let users = []; -let allUsers = []; -let filteredUsers = []; -let selectedUsers = []; -let editSelectedUsers = []; -let copySelectedUsers = []; - -async function loadUsers() { - try { - const response = await fetch('/api/users'); - const allUsersData = await response.json(); - //users = await response.json(); - // Сохраняем всех пользователей - allUsers = allUsersData; - // Фильтруем пользователей в зависимости от прав текущего пользователя - users = filterAssignableUsers(allUsersData); - filteredUsers = [...users]; - renderUsersChecklist(); - renderEditUsersChecklist(); - renderCopyUsersChecklist(); - populateFilterDropdowns(); - } catch (error) { - console.error('Ошибка загрузки пользователей:', error); - } -} -function filterAssignableUsers(allUsers) { - if (!currentUser) return []; - - // Администратор видит всех пользователей - if (currentUser.role === 'admin') { - return allUsers.filter(user => user.id !== currentUser.id); - } - if (currentUser.role === 'secretary') { - return allUsers.filter(user => user.id !== currentUser.id); - } - if (currentUser.role === 'ithelp') { - return allUsers.filter(user => - (user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') && - user.id !== currentUser.id - ); - } - if (currentUser.role === 'request') { - return allUsers.filter(user => - (user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') && - user.id !== currentUser.id - ); - } - // tasks видит учителей и других tasks - if (currentUser.role === 'help') { - return allUsers.filter(user => - (user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') && - user.id !== currentUser.id - ); - } - // tasks видит учителей и других tasks - if (currentUser.role === 'tasks') { - return allUsers.filter(user => - (user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') && - user.id !== currentUser.id - ); - } - // Учитель видит только учителей - if (currentUser.role === 'teacher') { - return allUsers.filter(user => - (user.role === 'help' || user.role === 'request' || user.role === 'ithelp') && - user.id !== currentUser.id - ); - } - // Проверяем группы пользователя для определения прав - const userGroups = currentUser.groups || []; - - // Загружаем конфигурацию групп из настроек - // (предполагается, что эти переменные определены в глобальной области) - const allowedGroups = getGroupsForCurrentUser(); - - // Если у пользователя нет специальных групп, возвращаем пустой массив - if (allowedGroups.length === 0) { - return []; - } - - // Фильтруем пользователей по группам - return allUsers.filter(user => { - // Пользователь не может назначать задачи себе - if (user.id === currentUser.id) return false; - - // Если у пользователя есть группы, проверяем пересечение - if (user.groups && Array.isArray(user.groups)) { - return user.groups.some(group => allowedGroups.includes(group)); - } - - return false; - }); -} - -// Функция для получения групп, которым текущий пользователь может назначать задачи -function getGroupsForCurrentUser() { - const allowedGroups = []; - const userGroups = currentUser.groups || []; - - // Определяем, какие группы доступны для назначения - // На основе ролей и групп текущего пользователя - - // Пример: пользователи с ролью 'secretary' могут назначать задачи группам 'teachers' - if (currentUser.role === 'secretary') { - allowedGroups.push('teachers', 'staff'); - } - - // Пример: пользователи из группы 'department_head' могут назначать своей группе - if (userGroups.includes('department_head')) { - allowedGroups.push('department_head', 'teachers'); - } - - // Пример: для помощи (help) можно назначать всем - if (currentUser.role === 'help') { - // Можно указать конкретные группы или 'all' для всех - allowedGroups.push('teachers', 'staff', 'administration'); - } - - // Пример: для IT поддержки - if (currentUser.role === 'ithelp') { - allowedGroups.push('teachers', 'staff', 'administration', 'it_department'); - } - - // Пример: пользователи с ролью 'request' могут создавать заявки для всех - if (currentUser.role === 'request') { - allowedGroups.push('all'); // Специальное значение "все" - } - - // Пример: пользователи с ролью 'tasks' (задачи) могут назначать учителям - if (currentUser.role === 'tasks') { - allowedGroups.push('teachers'); - } - - // Если массив содержит 'all', возвращаем специальный маркер - if (allowedGroups.includes('all')) { - return ['all']; // Это будет обрабатываться в фильтрации - } - - return [...new Set(allowedGroups)]; // Убираем дубликаты -} -function populateFilterDropdowns() { - const creatorFilter = document.getElementById('creator-filter'); - const assigneeFilter = document.getElementById('assignee-filter'); - - creatorFilter.innerHTML = ''; - assigneeFilter.innerHTML = ''; - - users.forEach(user => { - const creatorOption = document.createElement('option'); - creatorOption.value = user.id; - creatorOption.textContent = `${user.name} (${user.login})`; - creatorFilter.appendChild(creatorOption.cloneNode(true)); - - const assigneeOption = creatorOption.cloneNode(true); - assigneeFilter.appendChild(assigneeOption); - }); -} - -function filterUsers() { - const search = document.getElementById('user-search').value.toLowerCase(); - filteredUsers = users.filter(user => - user.name.toLowerCase().includes(search) || - user.login.toLowerCase().includes(search) || - user.email.toLowerCase().includes(search) - ); - renderUsersChecklist(); -} - -function filterEditUsers() { - const search = document.getElementById('edit-user-search').value.toLowerCase(); - const filtered = users.filter(user => - user.name.toLowerCase().includes(search) || - user.login.toLowerCase().includes(search) || - user.email.toLowerCase().includes(search) - ); - renderEditUsersChecklist(filtered); -} - -function filterCopyUsers() { - const search = document.getElementById('copy-user-search').value.toLowerCase(); - const filtered = users.filter(user => - user.name.toLowerCase().includes(search) || - user.login.toLowerCase().includes(search) || - user.email.toLowerCase().includes(search) - ); - renderCopyUsersChecklist(filtered); -} - -function renderUsersChecklist() { - const container = document.getElementById('users-checklist'); - container.innerHTML = filteredUsers - .filter(user => user.id !== currentUser.id) - .map(user => ` -
- -
- `).join(''); -} - -function renderEditUsersChecklist(filtered = users) { - const container = document.getElementById('edit-users-checklist'); - container.innerHTML = filtered - .filter(user => user.id !== currentUser.id) - .map(user => ` -
- -
- `).join(''); -} - -function renderCopyUsersChecklist(filtered = users) { - const container = document.getElementById('copy-users-checklist'); - container.innerHTML = filtered - .filter(user => user.id !== currentUser.id) - .map(user => ` -
- -
- `).join(''); -} - -function toggleUserSelection(checkbox, userId) { - if (checkbox.checked) { - selectedUsers.push(userId); - } else { - selectedUsers = selectedUsers.filter(id => id !== userId); - } -} - -function toggleEditUserSelection(checkbox, userId) { - if (checkbox.checked) { - editSelectedUsers.push(userId); - } else { - editSelectedUsers = editSelectedUsers.filter(id => id !== userId); - } -} - -function toggleCopyUserSelection(checkbox, userId) { - if (checkbox.checked) { - copySelectedUsers.push(userId); - } else { - copySelectedUsers = copySelectedUsers.filter(id => id !== userId); - } -} \ No newline at end of file diff --git a/public/doc.html b/public/doc.html deleted file mode 100644 index c48e059..0000000 --- a/public/doc.html +++ /dev/null @@ -1,1058 +0,0 @@ - - - - - - - Документация БД - - - -
-
-

📊 Документация Базы Данных

-

Просмотр и фильтрация данных таблиц SQLite

-
-
- -
-
-
- - - - - -
- -
- -
- -
- Записей: 0 - Показано: 0 - Обновлено: -- -
-
- -
-

База данных: SQLite

-

Таблицы: users, tasks, files, activity_logs, assignments

-
- -
-
-
-

Таблица: users (Пользователи)

- 0 записей -
-
- - - - - - - - - -
-
-
-
- -
-
-
-

Таблица: tasks (Задачи)

- 0 записей -
-
- - - - - - - - - -
-
-
-
- -
-
-
-

Таблица: files (Файлы)

- 0 записей -
-
- - - - - - - - - -
-
-
-
- -
-
-
-

Таблица: activity_logs (Логи активности)

- 0 записей -
-
- - - - - - - - - -
-
-
-
- -
-
-
-

Таблица: assignments (Назначения)

- 0 записей -
-
- - - - - - - - - -
-
-
-
- - -
- - - - \ No newline at end of file diff --git a/public/documents.js b/public/documents.js deleted file mode 100644 index 9ced2a0..0000000 --- a/public/documents.js +++ /dev/null @@ -1,646 +0,0 @@ -// documents.js - Работа с документами для согласования - -let documentTypes = []; - -async function loadDocumentTypes() { - try { - const response = await fetch('/api/document-types'); - documentTypes = await response.json(); - populateDocumentTypeSelect(); - } catch (error) { - console.error('Ошибка загрузки типов документов:', error); - } -} - -function populateDocumentTypeSelect() { - const select = document.getElementById('document-type'); - if (!select) return; - - select.innerHTML = ''; - - documentTypes.forEach(type => { - const option = document.createElement('option'); - option.value = type.id; - option.textContent = type.name; - select.appendChild(option); - }); -} - -function initializeDocumentForm() { - const form = document.getElementById('create-document-form'); - if (form) { - form.addEventListener('submit', createDocumentTask); - } - - // Инициализация даты по умолчанию - const today = new Date(); - const todayStr = today.toISOString().split('T')[0]; - const dateInput = document.getElementById('document-date'); - if (dateInput) { - dateInput.value = todayStr; - } - - loadDocumentTypes(); -} - -async function createDocumentTask() { - console.log('📝 Создание документа...'); - - // Собираем данные формы - const formData = new FormData(); - - // Обязательное поле - только название - const title = document.getElementById('doc-title').value.trim(); - if (!title) { - showNotification('Название документа обязательно', 'error'); - return; - } - - formData.append('title', title); - formData.append('description', document.getElementById('doc-description').value); - formData.append('dueDate', document.getElementById('doc-due-date').value); - - // Тип документа - опционально - const documentTypeSelect = document.getElementById('doc-type'); - if (documentTypeSelect && documentTypeSelect.value) { - formData.append('documentTypeId', documentTypeSelect.value); - } - - // Остальные поля - опционально - formData.append('documentNumber', document.getElementById('doc-number')?.value || ''); - formData.append('documentDate', document.getElementById('doc-date')?.value || ''); - formData.append('pagesCount', document.getElementById('doc-pages')?.value || ''); - - const urgencySelect = document.getElementById('doc-urgency'); - if (urgencySelect) { - formData.append('urgencyLevel', urgencySelect.value); - } - - formData.append('comment', document.getElementById('doc-comment')?.value || ''); - - // Добавляем файлы (опционально) - const fileInput = document.getElementById('doc-files'); - if (fileInput && fileInput.files) { - for (let i = 0; i < fileInput.files.length; i++) { - formData.append('files', fileInput.files[i]); - } - } - - // Показываем индикатор загрузки - const submitBtn = document.querySelector('#new-doc-form button[type="submit"]'); - const originalText = submitBtn.innerHTML; - submitBtn.innerHTML = ' Создание...'; - submitBtn.disabled = true; - - try { - console.log('📤 Отправка запроса на создание документа...'); - - const response = await fetch('/api/documents', { - method: 'POST', - body: formData - }); - - const result = await response.json(); - - if (response.ok) { - console.log('✅ Документ создан успешно:', result); - - // Показываем сообщение об успехе - showNotification('Документ успешно создан и отправлен на согласование', 'success'); - - // Закрываем модальное окно - const modal = bootstrap.Modal.getInstance(document.getElementById('newDocModal')); - if (modal) modal.hide(); - - // Очищаем форму - const form = document.getElementById('new-doc-form'); - if (form) form.reset(); - - // Обновляем список документов - await loadMyDocuments(); - - } else { - console.error('❌ Ошибка создания документа:', result); - showNotification(`Ошибка: ${result.error || 'Неизвестная ошибка'}`, 'error'); - } - - } catch (error) { - console.error('❌ Ошибка сети при создании документа:', error); - showNotification('Ошибка сети при создании документа', 'error'); - } finally { - // Восстанавливаем кнопку - if (submitBtn) { - submitBtn.innerHTML = originalText; - submitBtn.disabled = false; - } - } -} - -function updateDocumentFileList() { - const fileInput = document.getElementById('document-files'); - const fileList = document.getElementById('document-file-list'); - - const files = fileInput.files; - if (files.length === 0) { - fileList.innerHTML = ''; - return; - } - - let html = ''; - html += `

Общий размер: ${(totalSize / 1024 / 1024).toFixed(2)} MB / 300 MB

`; - - fileList.innerHTML = html; -} - -// Функции для работы с документами -async function loadMyDocuments() { - try { - const response = await fetch('/api/documents/my'); - const documents = await response.json(); - renderMyDocuments(documents); - } catch (error) { - console.error('Ошибка загрузки документов:', error); - } -} - -async function loadSecretaryDocuments() { - try { - const response = await fetch('/api/documents/secretary'); - const documents = await response.json(); - renderSecretaryDocuments(documents); - } catch (error) { - console.error('Ошибка загрузки документов секретаря:', error); - } -} - -function renderMyDocuments(documents) { - console.log('📄 Рендеринг документов:', documents); - - const container = document.getElementById('my-docs-list'); - - if (!documents || !Array.isArray(documents)) { - console.error('❌ documents не является массивом:', documents); - container.innerHTML = ` -
- -

Ошибка загрузки документов

-
- `; - return; - } - - if (documents.length === 0) { - container.innerHTML = ` -
- -

У вас нет документов для согласования

- -
- `; - return; - } - - container.innerHTML = documents.map(doc => { - // Определяем статус - let statusClass = 'status-pending'; - let statusText = 'На согласовании'; - - if (doc.assignment_status === 'completed' || doc.assignment_status === 'approved') { - statusClass = 'status-completed'; - statusText = 'Согласован'; - } else if (doc.assignment_status === 'refused') { - statusClass = 'status-cancelled'; - statusText = 'Отказано'; - } else if (doc.closed_at) { - statusClass = 'status-cancelled'; - statusText = 'Отозван'; - } - - // Форматируем дату - const createdDate = new Date(doc.created_at).toLocaleDateString('ru-RU'); - const dueDate = doc.due_date ? new Date(doc.due_date).toLocaleDateString('ru-RU') : 'Не указана'; - - // Определяем уровень срочности - let urgencyBadge = ''; - if (doc.urgency_level === 'urgent') { - urgencyBadge = 'Срочно'; - } else if (doc.urgency_level === 'very_urgent') { - urgencyBadge = 'Очень срочно'; - } - - // Проверяем наличие типа документа - const documentType = doc.document_type_name || 'Не указан'; - - return ` -
-
-

${doc.title.replace('Документ: ', '')}

- ${statusText} -
- -
-
- Тип: - ${documentType} -
- - ${doc.document_number ? ` -
- Номер: - ${doc.document_number} -
- ` : ''} - - ${doc.document_date ? ` -
- Дата документа: - ${new Date(doc.document_date).toLocaleDateString('ru-RU')} -
- ` : ''} - -
- Создан: - ${createdDate} -
- -
- Срок согласования: - ${dueDate} -
- - ${doc.urgency_level && doc.urgency_level !== 'normal' ? ` -
- Срочность: - ${urgencyBadge} -
- ` : ''} - - ${doc.assignee_name ? ` -
- Согласующий: - ${doc.assignee_name} -
- ` : ''} - - ${doc.refusal_reason ? ` -
- Причина отказа: - ${doc.refusal_reason} -
- ` : ''} -
- - ${doc.description ? ` -
-

${doc.description}

-
- ` : ''} - - ${doc.comment ? ` -
- Комментарий: -

${doc.comment}

-
- ` : ''} - - ${doc.files && doc.files.length > 0 ? ` -
- Файлы: -
- ${doc.files.map(file => ` - - ${file.original_name} (${formatFileSize(file.file_size)}) - - `).join('')} -
-
- ` : ''} - - ${!doc.closed_at && doc.assignment_status !== 'completed' && - doc.assignment_status !== 'approved' && doc.assignment_status !== 'refused' ? ` -
- -
- ` : ''} -
- `; - }).join(''); -} - -function formatFileSize(bytes) { - if (bytes === 0) return '0 Б'; - const k = 1024; - const sizes = ['Б', 'КБ', 'МБ', 'ГБ']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; -} - -// Исправьте функцию loadMyDocuments: -async function loadMyDocuments() { - console.log('📥 Загрузка моих документов...'); - - try { - const response = await fetch('/api/documents/my'); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const documents = await response.json(); - console.log('✅ Получены документы:', documents); - renderMyDocuments(documents); - } catch (error) { - console.error('❌ Ошибка загрузки документов:', error); - showNotification('Ошибка загрузки документов', 'error'); - - const container = document.getElementById('my-docs-list'); - container.innerHTML = ` -
- -

Ошибка загрузки документов: ${error.message}

-
- `; - } -} -function renderSecretaryDocuments(documents) { - const container = document.getElementById('secretary-documents-list'); - if (!container) return; - - if (documents.length === 0) { - container.innerHTML = '
Нет документов для согласования
'; - return; - } - - container.innerHTML = documents.map(doc => ` -
-
-
- Документ №${doc.document_number || doc.id} - ${doc.title} - ${getDocumentStatusText(doc.status)} - ${doc.urgency_level === 'urgent' ? 'Срочно' : ''} - ${doc.urgency_level === 'very_urgent' ? 'Очень срочно' : ''} -
-
- От: ${doc.creator_name} - Создан: ${formatDateTime(doc.created_at)} - ${doc.due_date ? `Срок: ${formatDateTime(doc.due_date)}` : ''} -
-
- -
-
-

Тип: ${doc.document_type_name || 'Не указан'}

-

Номер: ${doc.document_number || 'Не указан'}

-

Дата документа: ${doc.document_date ? formatDate(doc.document_date) : 'Не указана'}

-

Количество страниц: ${doc.pages_count || 'Не указано'}

- ${doc.comment ? `

Комментарий автора: ${doc.comment}

` : ''} -
- -
- ${doc.files && doc.files.length > 0 ? ` - Файлы: -
- ${doc.files.map(file => renderFileIcon(file)).join('')} -
- ` : 'Файлы: нет файлов'} -
- -
- ${doc.status === 'assigned' ? ` - - ` : ''} - - ${doc.status === 'in_progress' ? ` -
- - - -
- ` : ''} - - ${doc.status === 'approved' ? ` -
- - -
- ` : ''} - - ${doc.status === 'received' ? ` -
- - -
- ` : ''} - - ${doc.status === 'refused' ? ` -

Причина отказа: ${doc.refusal_reason}

- ` : ''} -
-
-
- `).join(''); -} - -function getDocumentStatusClass(status) { - switch(status) { - case 'assigned': return 'status-assigned'; - case 'in_progress': return 'status-in-progress'; - case 'approved': return 'status-approved'; - case 'received': return 'status-received'; - case 'signed': return 'status-signed'; - case 'refused': return 'status-refused'; - case 'cancelled': return 'status-cancelled'; - default: return 'status-assigned'; - } -} - -function getDocumentStatusText(status) { - switch(status) { - case 'assigned': return 'Назначена'; - case 'in_progress': return 'В работе'; - case 'approved': return 'Согласован'; - case 'received': return 'Получен'; - case 'signed': return 'Подписан'; - case 'refused': return 'Отказано'; - case 'cancelled': return 'Отозвано'; - default: return status; - } -} - -function formatDate(dateString) { - if (!dateString) return ''; - return new Date(dateString).toLocaleDateString('ru-RU'); -} - -// Модальные окна для секретаря -function showApproveModal(documentId) { - currentDocumentId = documentId; - document.getElementById('approve-document-modal').style.display = 'block'; -} - -function closeApproveModal() { - document.getElementById('approve-document-modal').style.display = 'none'; - document.getElementById('approve-comment').value = ''; -} - -function showReceiveModal(documentId) { - currentDocumentId = documentId; - document.getElementById('receive-document-modal').style.display = 'block'; -} - -function closeReceiveModal() { - document.getElementById('receive-document-modal').style.display = 'none'; - document.getElementById('receive-comment').value = ''; -} - -function showRefuseModal(documentId) { - currentDocumentId = documentId; - document.getElementById('refuse-document-modal').style.display = 'block'; -} - -function closeRefuseModal() { - document.getElementById('refuse-document-modal').style.display = 'none'; - document.getElementById('refuse-reason').value = ''; -} - -let currentDocumentId = null; - -// Функции для работы с API -async function updateDocumentStatus(documentId, status, comment = '', refusalReason = '') { - try { - const response = await fetch(`/api/documents/${documentId}/status`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - status: status, - comment: comment, - refusalReason: refusalReason - }) - }); - - if (response.ok) { - alert('Статус документа обновлен!'); - - // Закрываем модальные окна - closeApproveModal(); - closeReceiveModal(); - closeRefuseModal(); - - // Обновляем список документов - if (isSecretary()) { - loadSecretaryDocuments(); - } - loadMyDocuments(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка обновления статуса'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка обновления статуса'); - } -} - -async function cancelDocument(documentId) { - if (!confirm('Вы уверены, что хотите отозвать документ?')) { - return; - } - - try { - const response = await fetch(`/api/documents/${documentId}/cancel`, { - method: 'POST' - }); - - if (response.ok) { - alert('Документ отозван!'); - loadMyDocuments(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка отзыва документа'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка отзыва документа'); - } -} - -async function reworkDocument(documentId) { - // Здесь можно открыть форму для повторной отправки - alert('Функция исправления и повторной отправки будет реализована в следующей версии'); -} - -async function downloadDocumentPackage(documentId) { - try { - const response = await fetch(`/api/documents/${documentId}/package`); - if (response.ok) { - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `document_${documentId}_package.zip`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка скачивания пакета документов'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка скачивания пакета документов'); - } -} - -function isSecretary() { - return currentUser && currentUser.groups && currentUser.groups.includes('Секретарь'); -} - -function showDocumentSection(sectionName) { - // Скрываем все секции - document.querySelectorAll('.document-section').forEach(section => { - section.style.display = 'none'; - }); - - // Показываем выбранную секцию - const targetSection = document.getElementById(`${sectionName}-section`); - if (targetSection) { - targetSection.style.display = 'block'; - } - - // Загружаем данные для секции - if (sectionName === 'my-documents') { - loadMyDocuments(); - } else if (sectionName === 'secretary-documents' && isSecretary()) { - loadSecretaryDocuments(); - } -} - -// Инициализация при загрузке страницы -document.addEventListener('DOMContentLoaded', function() { - if (window.location.pathname === '/doc') { - initializeDocumentForm(); - - // Показываем соответствующие секции - if (isSecretary()) { - document.getElementById('secretary-tab').style.display = 'block'; - } - - // По умолчанию показываем создание документа - showDocumentSection('create-document'); - } -}); \ No newline at end of file diff --git a/server.js b/server.js index eb0fa3f..8e61faf 100644 --- a/server.js +++ b/server.js @@ -17,7 +17,9 @@ const { setupTaskEndpoints } = require('./task-endpoints'); const apiDoc = require('./api-doc'); // Подключаем API для управления исполнителями const userManagementAPI = require('./api-users'); - +// +const api2Groups = require('./api2-groups'); +// const app = express(); const PORT = process.env.PORT || 3000; @@ -1240,6 +1242,10 @@ async function initializeServer() { console.log('⚠️ Создана заглушка для админ роутера из-за ошибки'); } + // Подключаем API для внешних идентификаторов + api2Groups(app, db); + console.log('✅ API для внешних идентификаторов подключено'); + // 6. Помечаем сервер как готовый serverReady = true;