1279 lines
53 KiB
JavaScript
1279 lines
53 KiB
JavaScript
// admin-server.js
|
||
const express = require('express');
|
||
const router = express.Router();
|
||
|
||
// Middleware для проверки прав администратора
|
||
const requireAdmin = (req, res, next) => {
|
||
if (!req.session.user || req.session.user.role !== 'admin') {
|
||
return res.status(403).json({ error: 'Недостаточно прав' });
|
||
}
|
||
next();
|
||
};
|
||
|
||
// Статистика
|
||
router.get('/admin/stats', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
|
||
// Получаем статистику по задачам
|
||
const tasksStats = await new Promise((resolve, reject) => {
|
||
db.get(`
|
||
SELECT
|
||
COUNT(*) as totalTasks,
|
||
COUNT(CASE WHEN status = 'active' AND closed_at IS NULL THEN 1 END) as activeTasks,
|
||
COUNT(CASE WHEN closed_at IS NOT NULL THEN 1 END) as closedTasks,
|
||
COUNT(CASE WHEN status = 'deleted' THEN 1 END) as deletedTasks
|
||
FROM tasks
|
||
`, [], (err, row) => {
|
||
if (err) reject(err);
|
||
else resolve(row || { totalTasks: 0, activeTasks: 0, closedTasks: 0, deletedTasks: 0 });
|
||
});
|
||
});
|
||
|
||
// Статистика по назначениям
|
||
const assignmentsStats = await new Promise((resolve, reject) => {
|
||
db.get(`
|
||
SELECT
|
||
COUNT(*) as totalAssignments,
|
||
COUNT(CASE WHEN status = 'assigned' THEN 1 END) as assignedCount,
|
||
COUNT(CASE WHEN status = 'in_progress' THEN 1 END) as inProgressCount,
|
||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completedCount,
|
||
COUNT(CASE WHEN status = 'overdue' THEN 1 END) as overdueCount,
|
||
COUNT(CASE WHEN status = 'rework' THEN 1 END) as reworkCount
|
||
FROM task_assignments
|
||
`, [], (err, row) => {
|
||
if (err) reject(err);
|
||
else resolve(row || { totalAssignments: 0, assignedCount: 0, inProgressCount: 0, completedCount: 0, overdueCount: 0, reworkCount: 0 });
|
||
});
|
||
});
|
||
|
||
// Статистика по пользователям
|
||
const usersStats = await new Promise((resolve, reject) => {
|
||
db.get(`
|
||
SELECT
|
||
COUNT(*) as totalUsers,
|
||
COUNT(CASE WHEN role = 'admin' THEN 1 END) as adminUsers,
|
||
COUNT(CASE WHEN role = 'teacher' THEN 1 END) as teacherUsers,
|
||
COUNT(CASE WHEN auth_type = 'ldap' THEN 1 END) as ldapUsers,
|
||
COUNT(CASE WHEN auth_type = 'local' THEN 1 END) as localUsers
|
||
FROM users
|
||
`, [], (err, row) => {
|
||
if (err) reject(err);
|
||
else resolve(row || { totalUsers: 0, adminUsers: 0, teacherUsers: 0, ldapUsers: 0, localUsers: 0 });
|
||
});
|
||
});
|
||
|
||
// Статистика по файлам
|
||
const filesStats = await new Promise((resolve, reject) => {
|
||
db.get(`
|
||
SELECT
|
||
COUNT(*) as totalFiles,
|
||
SUM(file_size) as totalFilesSize
|
||
FROM task_files
|
||
`, [], (err, row) => {
|
||
if (err) reject(err);
|
||
else resolve(row || { totalFiles: 0, totalFilesSize: 0 });
|
||
});
|
||
});
|
||
|
||
res.json({
|
||
...tasksStats,
|
||
...assignmentsStats,
|
||
...usersStats,
|
||
...filesStats,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка получения статистики:', error);
|
||
res.status(500).json({ error: 'Ошибка получения статистики' });
|
||
}
|
||
});
|
||
|
||
// Получение всех пользователей
|
||
router.get('/admin/users', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
|
||
const query = `
|
||
SELECT id, login, name, email, role, auth_type, groups, description,
|
||
created_at, last_login, updated_at
|
||
FROM users
|
||
ORDER BY name
|
||
`;
|
||
|
||
db.all(query, [], (err, users) => {
|
||
if (err) {
|
||
console.error('Ошибка получения пользователей:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
res.json(users);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Получение конкретного пользователя
|
||
router.get('/admin/users/:id', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
|
||
const query = `
|
||
SELECT id, login, name, email, role, auth_type, groups, description,
|
||
created_at, last_login, updated_at
|
||
FROM users
|
||
WHERE id = ?
|
||
`;
|
||
|
||
db.get(query, [userId], (err, user) => {
|
||
if (err) {
|
||
console.error('Ошибка получения пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (!user) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
res.json(user);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Обновление пользователя
|
||
router.put('/admin/users/:id', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
const { login, name, email, role, auth_type, groups, description } = req.body;
|
||
|
||
if (!login || !name || !email) {
|
||
return res.status(400).json({ error: 'Заполните обязательные поля' });
|
||
}
|
||
|
||
// Проверяем, что пользователь не пытается изменить свою роль на не-админа
|
||
if (req.session.user.id == userId && role !== 'admin') {
|
||
return res.status(400).json({ error: 'Нельзя снять права администратора у себя' });
|
||
}
|
||
|
||
const query = `
|
||
UPDATE users
|
||
SET login = ?, name = ?, email = ?, role = ?, auth_type = ?,
|
||
groups = ?, description = ?, updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = ?
|
||
`;
|
||
|
||
db.run(query, [login, name, email, role, auth_type, groups || '[]', description || '', userId], function(err) {
|
||
if (err) {
|
||
console.error('Ошибка обновления пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
res.json({ success: true, message: 'Пользователь обновлен' });
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Удаление пользователя
|
||
router.delete('/admin/users/:id', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
|
||
// Нельзя удалить самого себя
|
||
if (req.session.user.id == userId) {
|
||
return res.status(400).json({ error: 'Нельзя удалить самого себя' });
|
||
}
|
||
|
||
const query = `DELETE FROM users WHERE id = ?`;
|
||
|
||
db.run(query, [userId], function(err) {
|
||
if (err) {
|
||
console.error('Ошибка удаления пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
res.json({ success: true, message: 'Пользователь удален' });
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Получение всех задач (для админа)
|
||
router.get('/admin/tasks', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const {
|
||
search = '',
|
||
status = 'all',
|
||
creator = '',
|
||
assignee = '',
|
||
limit = 100,
|
||
offset = 0
|
||
} = req.query;
|
||
|
||
let query = `
|
||
SELECT DISTINCT
|
||
t.*,
|
||
u.name as creator_name,
|
||
u.login as creator_login,
|
||
ot.title as original_task_title,
|
||
ou.name as original_creator_name,
|
||
GROUP_CONCAT(DISTINCT ta.user_id) as assigned_user_ids,
|
||
GROUP_CONCAT(DISTINCT u2.name) as assigned_user_names
|
||
FROM tasks t
|
||
LEFT JOIN users u ON t.created_by = u.id
|
||
LEFT JOIN tasks ot ON t.original_task_id = ot.id
|
||
LEFT JOIN users ou ON ot.created_by = ou.id
|
||
LEFT JOIN task_assignments ta ON t.id = ta.task_id
|
||
LEFT JOIN users u2 ON ta.user_id = u2.id
|
||
WHERE 1=1
|
||
`;
|
||
|
||
const params = [];
|
||
|
||
if (status !== 'all') {
|
||
if (status === 'deleted') {
|
||
query += " AND t.status = 'deleted'";
|
||
} else if (status === 'closed') {
|
||
query += " AND t.closed_at IS NOT NULL";
|
||
} else if (status === 'active') {
|
||
query += " AND t.status = 'active' AND t.closed_at IS NULL";
|
||
}
|
||
}
|
||
|
||
if (creator) {
|
||
query += ` AND t.created_by = ?`;
|
||
params.push(creator);
|
||
}
|
||
|
||
if (assignee) {
|
||
query += ` AND ta.user_id = ?`;
|
||
params.push(assignee);
|
||
}
|
||
|
||
if (search) {
|
||
query += ` AND (t.title LIKE ? OR t.description LIKE ?)`;
|
||
const searchPattern = `%${search}%`;
|
||
params.push(searchPattern, searchPattern);
|
||
}
|
||
|
||
query += " GROUP BY t.id ORDER BY t.created_at DESC LIMIT ? OFFSET ?";
|
||
params.push(parseInt(limit), parseInt(offset));
|
||
|
||
db.all(query, params, (err, tasks) => {
|
||
if (err) {
|
||
console.error('Ошибка получения задач:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
res.json(tasks);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Получение всех логов активности
|
||
router.get('/admin/activity-logs', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const {
|
||
taskId = '',
|
||
userId = '',
|
||
action = '',
|
||
startDate = '',
|
||
endDate = '',
|
||
limit = 100,
|
||
offset = 0
|
||
} = req.query;
|
||
|
||
let query = `
|
||
SELECT al.*, u.name as user_name, t.title as task_title
|
||
FROM activity_logs al
|
||
LEFT JOIN users u ON al.user_id = u.id
|
||
LEFT JOIN tasks t ON al.task_id = t.id
|
||
WHERE 1=1
|
||
`;
|
||
|
||
const params = [];
|
||
|
||
if (taskId) {
|
||
query += ` AND al.task_id = ?`;
|
||
params.push(taskId);
|
||
}
|
||
|
||
if (userId) {
|
||
query += ` AND al.user_id = ?`;
|
||
params.push(userId);
|
||
}
|
||
|
||
if (action) {
|
||
query += ` AND al.action = ?`;
|
||
params.push(action);
|
||
}
|
||
|
||
if (startDate) {
|
||
query += ` AND al.created_at >= ?`;
|
||
params.push(startDate);
|
||
}
|
||
|
||
if (endDate) {
|
||
query += ` AND al.created_at <= ?`;
|
||
params.push(endDate);
|
||
}
|
||
|
||
query += " ORDER BY al.created_at DESC LIMIT ? OFFSET ?";
|
||
params.push(parseInt(limit), parseInt(offset));
|
||
|
||
db.all(query, params, (err, logs) => {
|
||
if (err) {
|
||
console.error('Ошибка получения логов:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
res.json(logs);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Получение логов уведомлений
|
||
router.get('/admin/notification-logs', requireAdmin, async (req, res) => {
|
||
try {
|
||
const postgresLogger = require('./postgres');
|
||
|
||
const {
|
||
taskId,
|
||
status,
|
||
startDate,
|
||
endDate,
|
||
limit = 100,
|
||
offset = 0
|
||
} = req.query;
|
||
|
||
if (!postgresLogger.pool || !postgresLogger.initialized) {
|
||
return res.json({ logs: [], total: 0, limit, offset });
|
||
}
|
||
|
||
let query = `
|
||
SELECT * FROM sms_logs
|
||
WHERE 1=1
|
||
${taskId ? 'AND task_id = $1' : ''}
|
||
${status ? `AND status = $${taskId ? 2 : 1}` : ''}
|
||
${startDate ? `AND created_at >= $${taskId ? (status ? 3 : 2) : (status ? 2 : 1)}` : ''}
|
||
${endDate ? `AND created_at <= $${taskId ? (status ? (startDate ? 4 : 3) : (startDate ? 3 : 2)) : (status ? (startDate ? 3 : 2) : (startDate ? 2 : 1))}` : ''}
|
||
ORDER BY created_at DESC
|
||
LIMIT $${taskId ? (status ? (startDate ? (endDate ? 5 : 4) : (endDate ? 4 : 3)) : (startDate ? (endDate ? 4 : 3) : (endDate ? 3 : 2))) : (status ? (startDate ? (endDate ? 4 : 3) : (endDate ? 3 : 2)) : (startDate ? (endDate ? 3 : 2) : (endDate ? 2 : 1)))}
|
||
OFFSET $${taskId ? (status ? (startDate ? (endDate ? 6 : 5) : (endDate ? 5 : 4)) : (startDate ? (endDate ? 5 : 4) : (endDate ? 4 : 3))) : (status ? (startDate ? (endDate ? 5 : 4) : (endDate ? 4 : 3)) : (startDate ? (endDate ? 4 : 3) : (endDate ? 3 : 2)))}
|
||
`;
|
||
|
||
const params = [];
|
||
if (taskId) params.push(taskId);
|
||
if (status) params.push(status);
|
||
if (startDate) params.push(startDate);
|
||
if (endDate) params.push(endDate);
|
||
params.push(parseInt(limit), parseInt(offset));
|
||
|
||
const client = await postgresLogger.pool.connect();
|
||
try {
|
||
const result = await client.query(query, params);
|
||
const logs = result.rows;
|
||
|
||
// Получаем общее количество
|
||
const countQuery = `
|
||
SELECT COUNT(*) as total FROM sms_logs
|
||
WHERE 1=1
|
||
${taskId ? 'AND task_id = $1' : ''}
|
||
${status ? `AND status = $${taskId ? 2 : 1}` : ''}
|
||
${startDate ? `AND created_at >= $${taskId ? (status ? 3 : 2) : (status ? 2 : 1)}` : ''}
|
||
${endDate ? `AND created_at <= $${taskId ? (status ? (startDate ? 4 : 3) : (startDate ? 3 : 2)) : (status ? (startDate ? 3 : 2) : (startDate ? 2 : 1))}` : ''}
|
||
`;
|
||
|
||
const countResult = await client.query(countQuery, params.slice(0, -2));
|
||
const total = countResult.rows[0]?.total || 0;
|
||
|
||
res.json({
|
||
logs,
|
||
total: parseInt(total),
|
||
limit: parseInt(limit),
|
||
offset: parseInt(offset)
|
||
});
|
||
} finally {
|
||
client.release();
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка получения логов уведомлений:', error);
|
||
res.status(500).json({ error: 'Ошибка получения логов' });
|
||
}
|
||
});
|
||
|
||
// Получение детальной статистики
|
||
router.get('/admin/detailed-stats', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const { period = 'week' } = req.query;
|
||
|
||
let dateFilter = '';
|
||
let dateFormat = '';
|
||
|
||
switch (period) {
|
||
case 'day':
|
||
dateFilter = "created_at >= DATE('now', '-1 day')";
|
||
dateFormat = "strftime('%H:00', created_at)";
|
||
break;
|
||
case 'week':
|
||
dateFilter = "created_at >= DATE('now', '-7 days')";
|
||
dateFormat = "strftime('%Y-%m-%d', created_at)";
|
||
break;
|
||
case 'month':
|
||
dateFilter = "created_at >= DATE('now', '-30 days')";
|
||
dateFormat = "strftime('%Y-%m-%d', created_at)";
|
||
break;
|
||
case 'year':
|
||
dateFilter = "created_at >= DATE('now', '-365 days')";
|
||
dateFormat = "strftime('%Y-%m', created_at)";
|
||
break;
|
||
default:
|
||
dateFilter = "created_at >= DATE('now', '-7 days')";
|
||
dateFormat = "strftime('%Y-%m-%d', created_at)";
|
||
}
|
||
|
||
// Статистика по задачам по дням
|
||
const tasksByDay = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT
|
||
${dateFormat} as date,
|
||
COUNT(*) as total,
|
||
COUNT(CASE WHEN status = 'active' AND closed_at IS NULL THEN 1 END) as active,
|
||
COUNT(CASE WHEN closed_at IS NOT NULL THEN 1 END) as closed,
|
||
COUNT(CASE WHEN status = 'deleted' THEN 1 END) as deleted
|
||
FROM tasks
|
||
WHERE ${dateFilter}
|
||
GROUP BY ${dateFormat}
|
||
ORDER BY ${dateFormat}
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
|
||
// Статистика по назначениям по дням
|
||
const assignmentsByDay = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT
|
||
${dateFormat} as date,
|
||
COUNT(*) as total,
|
||
COUNT(CASE WHEN status = 'assigned' THEN 1 END) as assigned,
|
||
COUNT(CASE WHEN status = 'in_progress' THEN 1 END) as in_progress,
|
||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
||
COUNT(CASE WHEN status = 'overdue' THEN 1 END) as overdue,
|
||
COUNT(CASE WHEN status = 'rework' THEN 1 END) as rework
|
||
FROM task_assignments
|
||
WHERE ${dateFilter}
|
||
GROUP BY ${dateFormat}
|
||
ORDER BY ${dateFormat}
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
|
||
// Статистика по пользователям (активность)
|
||
const userActivity = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT
|
||
u.name as user_name,
|
||
u.login as user_login,
|
||
u.role,
|
||
u.auth_type,
|
||
COUNT(DISTINCT t.id) as tasks_created,
|
||
COUNT(DISTINCT ta.id) as tasks_assigned,
|
||
COUNT(DISTINCT CASE WHEN ta.status = 'completed' THEN ta.id END) as tasks_completed,
|
||
MAX(u.last_login) as last_login
|
||
FROM users u
|
||
LEFT JOIN tasks t ON u.id = t.created_by
|
||
LEFT JOIN task_assignments ta ON u.id = ta.user_id
|
||
GROUP BY u.id
|
||
ORDER BY tasks_created DESC
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
|
||
// Статистика по файлам
|
||
const filesStats = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT
|
||
${dateFormat} as date,
|
||
COUNT(*) as file_count,
|
||
SUM(file_size) as total_size
|
||
FROM task_files
|
||
WHERE ${dateFilter}
|
||
GROUP BY ${dateFormat}
|
||
ORDER BY ${dateFormat}
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
|
||
res.json({
|
||
period,
|
||
tasksByDay,
|
||
assignmentsByDay,
|
||
userActivity,
|
||
filesStats,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка получения детальной статистики:', error);
|
||
res.status(500).json({ error: 'Ошибка получения статистики' });
|
||
}
|
||
});
|
||
|
||
// Создание нового пользователя (только для админов)
|
||
router.post('/admin/users', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const { login, password, name, email, role = 'teacher', auth_type = 'local' } = req.body;
|
||
|
||
if (!login || !password || !name || !email) {
|
||
return res.status(400).json({ error: 'Заполните все обязательные поля' });
|
||
}
|
||
|
||
// Проверяем, существует ли пользователь с таким логином
|
||
db.get("SELECT id FROM users WHERE login = ?", [login], async (err, existingUser) => {
|
||
if (err) {
|
||
console.error('Ошибка проверки пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (existingUser) {
|
||
return res.status(400).json({ error: 'Пользователь с таким логином уже существует' });
|
||
}
|
||
|
||
// Проверяем, существует ли пользователь с таким email
|
||
db.get("SELECT id FROM users WHERE email = ?", [email], async (err, existingEmail) => {
|
||
if (err) {
|
||
console.error('Ошибка проверки email:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (existingEmail) {
|
||
return res.status(400).json({ error: 'Пользователь с таким email уже существует' });
|
||
}
|
||
|
||
// Хешируем пароль
|
||
const bcrypt = require('bcryptjs');
|
||
const hashedPassword = await bcrypt.hash(password, 10);
|
||
|
||
// Создаем пользователя
|
||
const query = `
|
||
INSERT INTO users (login, password, name, email, role, auth_type, created_at)
|
||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||
`;
|
||
|
||
db.run(query, [login, hashedPassword, name, email, role, auth_type], function(err) {
|
||
if (err) {
|
||
console.error('Ошибка создания пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Пользователь создан',
|
||
userId: this.lastID
|
||
});
|
||
});
|
||
});
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Сброс пароля пользователя
|
||
router.post('/admin/users/:id/reset-password', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
const { newPassword } = req.body;
|
||
|
||
if (!newPassword || newPassword.length < 6) {
|
||
return res.status(400).json({ error: 'Пароль должен содержать минимум 6 символов' });
|
||
}
|
||
|
||
// Хешируем пароль
|
||
const bcrypt = require('bcryptjs');
|
||
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||
|
||
const query = `UPDATE users SET password = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||
|
||
db.run(query, [hashedPassword, userId], function(err) {
|
||
if (err) {
|
||
console.error('Ошибка сброса пароля:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
res.json({ success: true, message: 'Пароль сброшен' });
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
// API для админ Канбан-доски
|
||
router.get('/admin/kanban-tasks', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const days = parseInt(req.query.days) || 7;
|
||
|
||
const now = new Date();
|
||
const futureDate = new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
|
||
const futureISO = futureDate.toISOString();
|
||
|
||
const query = `
|
||
SELECT DISTINCT
|
||
t.*,
|
||
u.name as creator_name,
|
||
u.login as creator_login,
|
||
GROUP_CONCAT(DISTINCT ta.user_id) as assigned_user_ids,
|
||
GROUP_CONCAT(DISTINCT u2.name) as assigned_user_names
|
||
FROM tasks t
|
||
LEFT JOIN users u ON t.created_by = u.id
|
||
LEFT JOIN task_assignments ta ON t.id = ta.task_id
|
||
LEFT JOIN users u2 ON ta.user_id = u2.id
|
||
WHERE t.status = 'active'
|
||
AND t.closed_at IS NULL
|
||
AND (t.due_date IS NULL OR t.due_date <= ?)
|
||
GROUP BY t.id
|
||
ORDER BY t.due_date ASC, t.created_at DESC
|
||
`;
|
||
|
||
db.all(query, [futureISO], async (err, tasks) => {
|
||
if (err) {
|
||
console.error('Ошибка получения задач для Канбана:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
// Добавляем статус для Канбана
|
||
const tasksWithKanban = await Promise.all(tasks.map(async (task) => {
|
||
// Получаем назначения для задачи
|
||
const assignments = await new Promise((resolve) => {
|
||
db.all(`
|
||
SELECT ta.*, u.name as user_name, u.login as user_login
|
||
FROM task_assignments ta
|
||
LEFT JOIN users u ON ta.user_id = u.id
|
||
WHERE ta.task_id = ?
|
||
`, [task.id], (err, rows) => {
|
||
resolve(rows || []);
|
||
});
|
||
});
|
||
|
||
// Определяем статус для Канбана
|
||
let kanbanStatus = 'unassigned';
|
||
|
||
if (assignments.length === 0) {
|
||
kanbanStatus = 'unassigned';
|
||
} else {
|
||
const hasAssigned = assignments.some(a => a.status === 'assigned');
|
||
const hasInProgress = assignments.some(a => a.status === 'in_progress');
|
||
const hasOverdue = assignments.some(a => a.status === 'overdue');
|
||
const hasRework = assignments.some(a => a.status === 'rework');
|
||
const allCompleted = assignments.every(a => a.status === 'completed');
|
||
|
||
if (allCompleted) {
|
||
kanbanStatus = 'completed';
|
||
} else if (hasRework) {
|
||
kanbanStatus = 'rework';
|
||
} else if (hasOverdue) {
|
||
kanbanStatus = 'overdue';
|
||
} else if (hasInProgress) {
|
||
kanbanStatus = 'in_progress';
|
||
} else if (hasAssigned) {
|
||
kanbanStatus = 'assigned';
|
||
}
|
||
}
|
||
|
||
return {
|
||
...task,
|
||
kanbanStatus,
|
||
assignments
|
||
};
|
||
}));
|
||
|
||
res.json({ tasks: tasksWithKanban });
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
// Экспорт данных
|
||
router.get('/admin/export', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const { format = 'json', dataType = 'all' } = req.query;
|
||
|
||
const exportData = {};
|
||
|
||
if (dataType === 'all' || dataType === 'users') {
|
||
const users = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT id, login, name, email, role, auth_type, groups, description,
|
||
created_at, last_login, updated_at
|
||
FROM users
|
||
ORDER BY id
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
exportData.users = users;
|
||
}
|
||
|
||
if (dataType === 'all' || dataType === 'tasks') {
|
||
const tasks = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT t.*, u.name as creator_name, u.login as creator_login
|
||
FROM tasks t
|
||
LEFT JOIN users u ON t.created_by = u.id
|
||
ORDER BY t.id
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
exportData.tasks = tasks;
|
||
}
|
||
|
||
if (dataType === 'all' || dataType === 'assignments') {
|
||
const assignments = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT ta.*, u.name as user_name, u.login as user_login, t.title as task_title
|
||
FROM task_assignments ta
|
||
LEFT JOIN users u ON ta.user_id = u.id
|
||
LEFT JOIN tasks t ON ta.task_id = t.id
|
||
ORDER BY ta.id
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
exportData.assignments = assignments;
|
||
}
|
||
|
||
if (dataType === 'all' || dataType === 'logs') {
|
||
const logs = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT al.*, u.name as user_name, t.title as task_title
|
||
FROM activity_logs al
|
||
LEFT JOIN users u ON al.user_id = u.id
|
||
LEFT JOIN tasks t ON al.task_id = t.id
|
||
ORDER BY al.id
|
||
`, [], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows || []);
|
||
});
|
||
});
|
||
exportData.activityLogs = logs;
|
||
}
|
||
|
||
exportData.exportedAt = new Date().toISOString();
|
||
exportData.system = 'MiniCRM';
|
||
exportData.version = '1.0.0';
|
||
|
||
if (format === 'csv') {
|
||
// Простая реализация CSV экспорта (можно расширить)
|
||
const csvData = Object.entries(exportData)
|
||
.map(([key, value]) => {
|
||
if (Array.isArray(value)) {
|
||
if (value.length === 0) return `${key}: Нет данных\n`;
|
||
const headers = Object.keys(value[0]).join(',');
|
||
const rows = value.map(item =>
|
||
Object.values(item).map(val =>
|
||
typeof val === 'string' ? `"${val.replace(/"/g, '""')}"` : val
|
||
).join(',')
|
||
).join('\n');
|
||
return `${key}:\n${headers}\n${rows}\n\n`;
|
||
}
|
||
return `${key}: ${JSON.stringify(value)}\n`;
|
||
})
|
||
.join('');
|
||
|
||
res.setHeader('Content-Type', 'text/csv');
|
||
res.setHeader('Content-Disposition', `attachment; filename="minicrm_export_${new Date().toISOString().split('T')[0]}.csv"`);
|
||
res.send(csvData);
|
||
} else {
|
||
res.json(exportData);
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка экспорта данных:', error);
|
||
res.status(500).json({ error: 'Ошибка экспорта данных' });
|
||
}
|
||
});
|
||
|
||
// Получение профилей пользователей с настройками уведомлений
|
||
router.get('/admin/user-profiles', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
|
||
const query = `
|
||
SELECT
|
||
u.id,
|
||
u.login,
|
||
u.name,
|
||
u.email as user_email,
|
||
u.role,
|
||
u.auth_type,
|
||
u.groups,
|
||
u.created_at,
|
||
u.last_login,
|
||
us.email_notifications,
|
||
us.notification_email,
|
||
us.telegram_notifications,
|
||
us.telegram_chat_id,
|
||
us.vk_notifications,
|
||
us.vk_user_id,
|
||
us.updated_at as settings_updated_at
|
||
FROM users u
|
||
LEFT JOIN user_settings us ON u.id = us.user_id
|
||
ORDER BY u.name
|
||
`;
|
||
|
||
db.all(query, [], (err, profiles) => {
|
||
if (err) {
|
||
console.error('Ошибка получения профилей пользователей:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
res.json(profiles);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Получение конкретного профиля пользователя
|
||
router.get('/admin/user-profiles/:id', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
|
||
const query = `
|
||
SELECT
|
||
u.id,
|
||
u.login,
|
||
u.name,
|
||
u.email as user_email,
|
||
u.role,
|
||
u.auth_type,
|
||
u.groups,
|
||
u.created_at,
|
||
u.last_login,
|
||
us.email_notifications,
|
||
us.notification_email,
|
||
us.telegram_notifications,
|
||
us.telegram_chat_id,
|
||
us.vk_notifications,
|
||
us.vk_user_id,
|
||
us.created_at as settings_created_at,
|
||
us.updated_at as settings_updated_at
|
||
FROM users u
|
||
LEFT JOIN user_settings us ON u.id = us.user_id
|
||
WHERE u.id = ?
|
||
`;
|
||
|
||
db.get(query, [userId], (err, profile) => {
|
||
if (err) {
|
||
console.error('Ошибка получения профиля пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (!profile) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
res.json(profile);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Обновление настроек уведомлений пользователя (для админа)
|
||
router.put('/admin/user-profiles/:id/notification-settings', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
const {
|
||
email_notifications,
|
||
notification_email,
|
||
telegram_notifications,
|
||
telegram_chat_id,
|
||
vk_notifications,
|
||
vk_user_id
|
||
} = req.body;
|
||
|
||
// Валидация email
|
||
if (email_notifications && notification_email) {
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(notification_email)) {
|
||
return res.status(400).json({ error: 'Неверный формат email' });
|
||
}
|
||
}
|
||
|
||
// Проверяем существование записи
|
||
db.get("SELECT id FROM user_settings WHERE user_id = ?", [userId], (err, existing) => {
|
||
if (err) {
|
||
console.error('Ошибка проверки настроек:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (existing) {
|
||
// Обновляем существующие настройки
|
||
db.run(
|
||
`UPDATE user_settings SET
|
||
email_notifications = ?,
|
||
notification_email = ?,
|
||
telegram_notifications = ?,
|
||
telegram_chat_id = ?,
|
||
vk_notifications = ?,
|
||
vk_user_id = ?,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE user_id = ?`,
|
||
[
|
||
email_notifications ? 1 : 0,
|
||
notification_email || '',
|
||
telegram_notifications ? 1 : 0,
|
||
telegram_chat_id || '',
|
||
vk_notifications ? 1 : 0,
|
||
vk_user_id || '',
|
||
userId
|
||
],
|
||
function(updateErr) {
|
||
if (updateErr) {
|
||
console.error('Ошибка обновления настроек:', updateErr);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
// Логируем действие
|
||
const { logActivity } = require('./database');
|
||
if (logActivity) {
|
||
logActivity(0, req.session.user.id, 'USER_SETTINGS_UPDATED', `Админ обновил настройки уведомлений пользователя ${userId}`);
|
||
}
|
||
|
||
console.log(`✅ Админ обновил настройки пользователя ${userId}`);
|
||
res.json({ success: true, message: 'Настройки уведомлений обновлены' });
|
||
}
|
||
);
|
||
} else {
|
||
// Создаем новые настройки
|
||
db.run(
|
||
`INSERT INTO user_settings
|
||
(user_id, email_notifications, notification_email,
|
||
telegram_notifications, telegram_chat_id,
|
||
vk_notifications, vk_user_id)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
userId,
|
||
email_notifications ? 1 : 0,
|
||
notification_email || '',
|
||
telegram_notifications ? 1 : 0,
|
||
telegram_chat_id || '',
|
||
vk_notifications ? 1 : 0,
|
||
vk_user_id || ''
|
||
],
|
||
function(insertErr) {
|
||
if (insertErr) {
|
||
console.error('Ошибка создания настроек:', insertErr);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
// Логируем действие
|
||
const { logActivity } = require('./database');
|
||
if (logActivity) {
|
||
logActivity(null, req.session.user.id, 'USER_SETTINGS_CREATED', `Админ создал настройки уведомлений пользователя ${userId}`);
|
||
}
|
||
|
||
console.log(`✅ Админ создал настройки пользователя ${userId}`);
|
||
res.json({ success: true, message: 'Настройки уведомлений созданы' });
|
||
}
|
||
);
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Массовое обновление email уведомлений для нескольких пользователей
|
||
router.post('/admin/bulk-email-settings', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const { users, email_notifications, notification_email } = req.body;
|
||
|
||
if (!Array.isArray(users) || users.length === 0) {
|
||
return res.status(400).json({ error: 'Выберите пользователей' });
|
||
}
|
||
|
||
if (email_notifications && !notification_email) {
|
||
return res.status(400).json({ error: 'Укажите email для уведомлений' });
|
||
}
|
||
|
||
// Валидация email
|
||
if (email_notifications && notification_email) {
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(notification_email)) {
|
||
return res.status(400).json({ error: 'Неверный формат email' });
|
||
}
|
||
}
|
||
|
||
const results = {
|
||
success: 0,
|
||
failed: 0,
|
||
errors: []
|
||
};
|
||
|
||
// Обновляем каждого пользователя в транзакции
|
||
db.serialize(() => {
|
||
db.run("BEGIN TRANSACTION");
|
||
|
||
users.forEach(userId => {
|
||
// Проверяем существование записи
|
||
db.get("SELECT id FROM user_settings WHERE user_id = ?", [userId], (err, existing) => {
|
||
if (err) {
|
||
results.failed++;
|
||
results.errors.push({ userId, error: err.message });
|
||
return;
|
||
}
|
||
|
||
if (existing) {
|
||
// Обновляем существующие настройки
|
||
db.run(
|
||
`UPDATE user_settings SET
|
||
email_notifications = ?,
|
||
notification_email = ?,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE user_id = ?`,
|
||
[
|
||
email_notifications ? 1 : 0,
|
||
email_notifications ? notification_email : '',
|
||
userId
|
||
],
|
||
function(updateErr) {
|
||
if (updateErr) {
|
||
results.failed++;
|
||
results.errors.push({ userId, error: updateErr.message });
|
||
} else {
|
||
results.success++;
|
||
}
|
||
}
|
||
);
|
||
} else {
|
||
// Создаем новые настройки с значениями по умолчанию
|
||
db.run(
|
||
`INSERT INTO user_settings
|
||
(user_id, email_notifications, notification_email,
|
||
telegram_notifications, telegram_chat_id,
|
||
vk_notifications, vk_user_id)
|
||
VALUES (?, ?, ?, 0, '', 0, '')`,
|
||
[
|
||
userId,
|
||
email_notifications ? 1 : 0,
|
||
email_notifications ? notification_email : ''
|
||
],
|
||
function(insertErr) {
|
||
if (insertErr) {
|
||
results.failed++;
|
||
results.errors.push({ userId, error: insertErr.message });
|
||
} else {
|
||
results.success++;
|
||
}
|
||
}
|
||
);
|
||
}
|
||
});
|
||
});
|
||
|
||
setTimeout(() => {
|
||
db.run("COMMIT", (commitErr) => {
|
||
if (commitErr) {
|
||
console.error('Ошибка коммита транзакции:', commitErr);
|
||
return res.status(500).json({ error: 'Ошибка транзакции' });
|
||
}
|
||
|
||
// Логируем действие
|
||
const { logActivity } = require('./database');
|
||
if (logActivity) {
|
||
logActivity(null, req.session.user.id, 'BULK_SETTINGS_UPDATED',
|
||
`Админ массово обновил настройки для ${users.length} пользователей`);
|
||
}
|
||
|
||
console.log(`✅ Массовое обновление: успешно ${results.success}, ошибок ${results.failed}`);
|
||
res.json({
|
||
success: true,
|
||
message: 'Настройки обновлены',
|
||
results
|
||
});
|
||
});
|
||
}, 1000);
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
// Отправка тестового уведомления пользователю
|
||
router.post('/admin/user-profiles/:id/test-notification', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { getDb } = require('./database');
|
||
const db = getDb();
|
||
const userId = req.params.id;
|
||
const notificationType = req.body.notification_type || 'test';
|
||
|
||
// Получаем информацию о пользователе
|
||
db.get(`
|
||
SELECT u.id, u.name, u.email, us.email_notifications, us.notification_email
|
||
FROM users u
|
||
LEFT JOIN user_settings us ON u.id = us.user_id
|
||
WHERE u.id = ?
|
||
`, [userId], async (err, user) => {
|
||
if (err) {
|
||
console.error('Ошибка получения пользователя:', err);
|
||
return res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
|
||
if (!user) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
// Проверяем, включены ли email уведомления
|
||
if (!user.email_notifications) {
|
||
return res.status(400).json({ error: 'Email уведомления отключены у пользователя' });
|
||
}
|
||
|
||
const emailTo = user.notification_email || user.email;
|
||
if (!emailTo) {
|
||
return res.status(400).json({ error: 'У пользователя не указан email' });
|
||
}
|
||
|
||
// Отправляем тестовое уведомление
|
||
const emailNotifications = require('./email-notifications');
|
||
const emailSent = await emailNotifications.sendEmailNotification(
|
||
emailTo,
|
||
'Тестовое уведомление от School CRM',
|
||
`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; line-height: 1.6; }
|
||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px 10px 0 0; }
|
||
.content { padding: 20px; border: 1px solid #ddd; border-top: none; }
|
||
.button { display: inline-block; background: #667eea; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
|
||
.footer { margin-top: 20px; font-size: 12px; color: #666; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h2>✅ Тестовое уведомление</h2>
|
||
</div>
|
||
<div class="content">
|
||
<p>Здравствуйте, ${user.name}!</p>
|
||
<p>Это тестовое уведомление от системы School CRM.</p>
|
||
<p>Если вы получили это письмо, значит настройки уведомлений работают корректно.</p>
|
||
<br>
|
||
<p><strong>Информация о тесте:</strong></p>
|
||
<ul>
|
||
<li>Получатель: ${emailTo}</li>
|
||
<li>Отправитель: Администратор</li>
|
||
<li>Время: ${new Date().toLocaleString('ru-RU')}</li>
|
||
</ul>
|
||
</div>
|
||
<div class="footer">
|
||
<p>Это тестовое сообщение от School CRM системы.</p>
|
||
<p>Вы можете изменить настройки уведомлений в личном кабинете.</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`
|
||
);
|
||
|
||
if (emailSent) {
|
||
// Логируем действие
|
||
const { logActivity } = require('./database');
|
||
if (logActivity) {
|
||
logActivity(null, req.session.user.id, 'TEST_NOTIFICATION_SENT',
|
||
`Админ отправил тестовое уведомление пользователю ${user.name} (${emailTo})`);
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Тестовое уведомление отправлено',
|
||
email: emailTo
|
||
});
|
||
} else {
|
||
res.status(500).json({ error: 'Не удалось отправить уведомление' });
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
});
|
||
|
||
module.exports = router; |