1322 lines
53 KiB
JavaScript
1322 lines
53 KiB
JavaScript
const express = require('express');
|
||
const multer = require('multer');
|
||
const path = require('path');
|
||
const fs = require('fs');
|
||
const session = require('express-session');
|
||
const fetch = require('node-fetch');
|
||
require('dotenv').config();
|
||
|
||
const { db, logActivity, createUserTaskFolder, saveTaskMetadata, updateTaskMetadata, checkTaskAccess } = require('./database');
|
||
const authService = require('./auth');
|
||
const adminRouter = require('./admin-server');
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 3000;
|
||
|
||
app.use(express.json());
|
||
app.use(express.urlencoded({ extended: true }));
|
||
app.use(express.static('public'));
|
||
app.use('/uploads', express.static(path.join(__dirname, 'data', 'uploads')));
|
||
app.use(session({
|
||
secret: process.env.SESSION_SECRET || 'fallback_secret_change_in_production',
|
||
resave: true,
|
||
saveUninitialized: false,
|
||
cookie: {
|
||
secure: false,
|
||
maxAge: 24 * 60 * 60 * 1000,
|
||
httpOnly: true
|
||
}
|
||
}));
|
||
|
||
app.use(adminRouter);
|
||
|
||
const requireAuth = (req, res, next) => {
|
||
if (!req.session.user) {
|
||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||
}
|
||
next();
|
||
};
|
||
|
||
const storage = multer.diskStorage({
|
||
destination: (req, file, cb) => {
|
||
const taskId = req.body.taskId || req.params.taskId;
|
||
const userLogin = req.session.user.login;
|
||
|
||
if (taskId) {
|
||
const userFolder = createUserTaskFolder(taskId, userLogin);
|
||
cb(null, userFolder);
|
||
} else {
|
||
const tempDir = path.join(__dirname, 'data', 'uploads', 'temp');
|
||
if (!fs.existsSync(tempDir)) {
|
||
fs.mkdirSync(tempDir, { recursive: true });
|
||
}
|
||
cb(null, tempDir);
|
||
}
|
||
},
|
||
filename: (req, file, cb) => {
|
||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||
cb(null, uniqueSuffix + path.extname(file.originalname));
|
||
}
|
||
});
|
||
|
||
const upload = multer({
|
||
storage: storage,
|
||
limits: {
|
||
fileSize: 300 * 1024 * 1024,
|
||
files: 15
|
||
}
|
||
});
|
||
|
||
const createDirIfNotExists = (dirPath) => {
|
||
if (!fs.existsSync(dirPath)) {
|
||
fs.mkdirSync(dirPath, { recursive: true });
|
||
}
|
||
};
|
||
|
||
function checkIfOverdue(dueDate, status) {
|
||
if (!dueDate || status === 'completed') return false;
|
||
const now = new Date();
|
||
const due = new Date(dueDate);
|
||
return due < now;
|
||
}
|
||
|
||
function checkOverdueTasks() {
|
||
const now = new Date().toISOString();
|
||
|
||
const query = `
|
||
SELECT ta.id, ta.task_id, ta.user_id, ta.status, ta.due_date
|
||
FROM task_assignments ta
|
||
JOIN tasks t ON ta.task_id = t.id
|
||
WHERE ta.due_date IS NOT NULL
|
||
AND ta.due_date < ?
|
||
AND ta.status NOT IN ('completed', 'overdue')
|
||
AND t.status = 'active'
|
||
AND t.closed_at IS NULL
|
||
`;
|
||
|
||
db.all(query, [now], (err, assignments) => {
|
||
if (err) {
|
||
console.error('Ошибка при проверке просроченных задач:', err);
|
||
return;
|
||
}
|
||
|
||
assignments.forEach(assignment => {
|
||
db.run(
|
||
"UPDATE task_assignments SET status = 'overdue' WHERE id = ?",
|
||
[assignment.id]
|
||
);
|
||
logActivity(assignment.task_id, assignment.user_id, 'STATUS_CHANGED', 'Задача просрочена');
|
||
});
|
||
});
|
||
}
|
||
|
||
function checkUpcomingDeadlines() {
|
||
const now = new Date();
|
||
const in48Hours = new Date(now.getTime() + 48 * 60 * 60 * 1000).toISOString();
|
||
const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
||
const nowISO = now.toISOString();
|
||
|
||
const query = `
|
||
SELECT DISTINCT ta.*, t.title, t.description, t.created_by, u.name as user_name, u.email as user_email,
|
||
creator.name as creator_name, creator.email as creator_email
|
||
FROM task_assignments ta
|
||
JOIN tasks t ON ta.task_id = t.id
|
||
JOIN users u ON ta.user_id = u.id
|
||
JOIN users creator ON t.created_by = creator.id
|
||
WHERE ta.due_date IS NOT NULL
|
||
AND ta.due_date > ?
|
||
AND ta.due_date <= ?
|
||
AND ta.status NOT IN ('completed', 'overdue')
|
||
AND t.status = 'active'
|
||
AND t.closed_at IS NULL
|
||
`;
|
||
|
||
db.all(query, [nowISO, in48Hours], async (err, assignments) => {
|
||
if (err) {
|
||
console.error('Ошибка при проверке сроков задач:', err);
|
||
return;
|
||
}
|
||
|
||
for (const assignment of assignments) {
|
||
const dueDate = new Date(assignment.due_date);
|
||
const timeLeft = dueDate.getTime() - now.getTime();
|
||
const hoursLeft = Math.floor(timeLeft / (60 * 60 * 1000));
|
||
|
||
if (hoursLeft <= 48 && hoursLeft > 24) {
|
||
await sendDeadlineNotification(assignment, 48);
|
||
} else if (hoursLeft <= 24) {
|
||
await sendDeadlineNotification(assignment, 24);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
async function sendDeadlineNotification(assignment, hoursLeft) {
|
||
try {
|
||
if (!process.env.NOTIFICATION_SERVICE_URL ||
|
||
!process.env.NOTIFICATION_SERVICE_LOGIN ||
|
||
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
|
||
return;
|
||
}
|
||
|
||
const notificationKey = `deadline_${hoursLeft}h_task_${assignment.task_id}_user_${assignment.user_id}`;
|
||
const lastSent = await getLastNotificationSent(notificationKey);
|
||
const now = new Date();
|
||
|
||
if (lastSent) {
|
||
const timeSinceLast = now.getTime() - new Date(lastSent).getTime();
|
||
if (timeSinceLast < 12 * 60 * 60 * 1000) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
const subject = `⚠️ До окончания срока задачи осталось менее ${hoursLeft} часов`;
|
||
const content = `Задача: ${assignment.title}\n\n` +
|
||
`Описание: ${assignment.description || 'Без описания'}\n` +
|
||
`Срок выполнения: ${new Date(assignment.due_date).toLocaleString('ru-RU')}\n` +
|
||
`Осталось времени: ${hoursLeft} часов\n\n` +
|
||
`Пожалуйста, завершите задачу в срок.`;
|
||
|
||
const recipients = [
|
||
{ id: assignment.user_id, name: assignment.user_name, email: assignment.user_email },
|
||
{ id: assignment.created_by, name: assignment.creator_name, email: assignment.creator_email }
|
||
].filter((value, index, self) =>
|
||
self.findIndex(r => r.id === value.id) === index
|
||
);
|
||
|
||
const recipientIds = recipients.map(r => r.id);
|
||
|
||
const authHeader = Buffer.from(
|
||
`${process.env.NOTIFICATION_SERVICE_LOGIN}:${process.env.NOTIFICATION_SERVICE_PASSWORD}`
|
||
).toString('base64');
|
||
|
||
const FormData = require('form-data');
|
||
const formData = new FormData();
|
||
formData.append('subject', subject);
|
||
formData.append('content', content);
|
||
formData.append('recipients', JSON.stringify(recipientIds));
|
||
formData.append('deliveryMethods', JSON.stringify(['email', 'telegram', 'vk']));
|
||
|
||
const response = await fetch(process.env.NOTIFICATION_SERVICE_URL, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Basic ${authHeader}`
|
||
},
|
||
body: formData
|
||
});
|
||
|
||
if (response.ok) {
|
||
await saveNotificationSent(notificationKey);
|
||
console.log(`✅ Уведомление о сроке (${hoursLeft}ч) отправлено для задачи ${assignment.task_id}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Ошибка отправки уведомления о сроке:', error);
|
||
}
|
||
}
|
||
|
||
function getLastNotificationSent(key) {
|
||
return new Promise((resolve) => {
|
||
db.get("SELECT created_at FROM notification_logs WHERE notification_key = ? ORDER BY created_at DESC LIMIT 1",
|
||
[key], (err, row) => {
|
||
resolve(row ? row.created_at : null);
|
||
}
|
||
);
|
||
});
|
||
}
|
||
|
||
function saveNotificationSent(key) {
|
||
db.run("INSERT INTO notification_logs (notification_key) VALUES (?)", [key]);
|
||
}
|
||
|
||
function encodeBasicAuth(login, password) {
|
||
return Buffer.from(`${login}:${password}`).toString('base64');
|
||
}
|
||
|
||
async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, authorId, comment = '', status = '', userName = '') {
|
||
try {
|
||
if (!process.env.NOTIFICATION_SERVICE_URL ||
|
||
!process.env.NOTIFICATION_SERVICE_LOGIN ||
|
||
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
|
||
console.log('Настройки сервиса уведомлений не заданы');
|
||
return;
|
||
}
|
||
|
||
const participants = await new Promise((resolve, reject) => {
|
||
db.all(`
|
||
SELECT t.created_by as user_id, u.name as user_name, u.login as user_login, u.email, 'creator' as role
|
||
FROM tasks t
|
||
LEFT JOIN users u ON t.created_by = u.id
|
||
WHERE t.id = ?
|
||
|
||
UNION
|
||
|
||
SELECT ta.user_id, u.name as user_name, u.login as user_login, u.email, 'assignee' as role
|
||
FROM task_assignments ta
|
||
LEFT JOIN users u ON ta.user_id = u.id
|
||
WHERE ta.task_id = ?
|
||
`, [taskId, taskId], (err, rows) => {
|
||
if (err) reject(err);
|
||
else resolve(rows);
|
||
});
|
||
});
|
||
|
||
if (!participants || participants.length === 0) {
|
||
console.log('Нет участников для уведомления');
|
||
return;
|
||
}
|
||
|
||
const author = await new Promise((resolve, reject) => {
|
||
db.get("SELECT name FROM users WHERE id = ?", [authorId], (err, row) => {
|
||
if (err) reject(err);
|
||
else resolve(row);
|
||
});
|
||
});
|
||
|
||
const authorName = author ? author.name : 'Система';
|
||
|
||
let subject, content;
|
||
|
||
switch (type) {
|
||
case 'created':
|
||
subject = `Новая задача: ${taskTitle}`;
|
||
content = `Создана новая задача:\n\n` +
|
||
`📋 ${taskTitle}\n` +
|
||
`📝 ${taskDescription || 'Без описания'}\n` +
|
||
`👤 Автор: ${authorName}\n\n` +
|
||
`Для просмотра перейдите в систему управления задачами.`;
|
||
break;
|
||
|
||
case 'updated':
|
||
subject = `Обновлена задача: ${taskTitle}`;
|
||
content = `Задача была обновлена:\n\n` +
|
||
`📋 ${taskTitle}\n` +
|
||
`📝 ${taskDescription || 'Без описания'}\n` +
|
||
`👤 Изменено: ${authorName}\n\n` +
|
||
`Для просмотра изменений перейдите в систему управления задачами.`;
|
||
break;
|
||
|
||
case 'rework':
|
||
subject = `Задача возвращена на доработку: ${taskTitle}`;
|
||
content = `Задача возвращена на доработку:\n\n` +
|
||
`📋 ${taskTitle}\n` +
|
||
`📝 Комментарий: ${comment}\n` +
|
||
`👤 Автор замечания: ${authorName}\n\n` +
|
||
`Пожалуйста, исправьте замечания и обновите статус задачи.`;
|
||
break;
|
||
|
||
case 'closed':
|
||
subject = `Задача закрыта: ${taskTitle}`;
|
||
content = `Задача была закрыта:\n\n` +
|
||
`📋 ${taskTitle}\n` +
|
||
`👤 Закрыта: ${authorName}\n\n` +
|
||
`Задача завершена и перемещена в архив.`;
|
||
break;
|
||
|
||
case 'status_changed':
|
||
const statusText = getStatusText(status);
|
||
subject = `Изменен статус задачи: ${taskTitle}`;
|
||
content = `Статус задачи изменен:\n\n` +
|
||
`📋 ${taskTitle}\n` +
|
||
`🔄 Новый статус: ${statusText}\n` +
|
||
`👤 Изменил: ${userName || authorName}\n\n` +
|
||
`Для просмотра перейдите в систему управления задачами.`;
|
||
break;
|
||
|
||
default:
|
||
return;
|
||
}
|
||
|
||
const recipientIds = participants
|
||
.filter(p => p.user_id !== authorId)
|
||
.map(p => p.user_id);
|
||
|
||
if (recipientIds.length === 0) {
|
||
console.log('Нет получателей для уведомления (все участники - автор изменения)');
|
||
return;
|
||
}
|
||
|
||
const authHeader = encodeBasicAuth(
|
||
process.env.NOTIFICATION_SERVICE_LOGIN,
|
||
process.env.NOTIFICATION_SERVICE_PASSWORD
|
||
);
|
||
|
||
const FormData = require('form-data');
|
||
const formData = new FormData();
|
||
formData.append('subject', subject);
|
||
formData.append('content', content);
|
||
formData.append('recipients', JSON.stringify(recipientIds));
|
||
formData.append('deliveryMethods', JSON.stringify(['email', 'telegram', 'vk']));
|
||
|
||
const response = await fetch(process.env.NOTIFICATION_SERVICE_URL, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Basic ${authHeader}`
|
||
},
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log(`✅ Уведомления отправлены для задачи ${taskId}:`, {
|
||
type: type,
|
||
recipients: recipientIds.length,
|
||
authorExcluded: authorId
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('❌ Ошибка отправки уведомлений:', error);
|
||
}
|
||
}
|
||
|
||
function getStatusText(status) {
|
||
const statusMap = {
|
||
'assigned': 'Назначена',
|
||
'in_progress': 'В работе',
|
||
'completed': 'Завершена',
|
||
'overdue': 'Просрочена',
|
||
'rework': 'На доработке'
|
||
};
|
||
return statusMap[status] || status;
|
||
}
|
||
|
||
app.post('/api/login', async (req, res) => {
|
||
const { login, password } = req.body;
|
||
|
||
if (!login || !password) {
|
||
return res.status(400).json({ error: 'Логин и пароль обязательны' });
|
||
}
|
||
|
||
try {
|
||
const user = await authService.authenticate(login, password);
|
||
if (user) {
|
||
const sessionUser = {
|
||
id: user.id,
|
||
login: user.login,
|
||
name: user.name,
|
||
email: user.email,
|
||
role: user.role,
|
||
auth_type: user.auth_type,
|
||
groups: user.groups ? (typeof user.groups === 'string' ? JSON.parse(user.groups) : user.groups) : []
|
||
};
|
||
|
||
req.session.user = sessionUser;
|
||
|
||
req.session.save((err) => {
|
||
if (err) {
|
||
console.error('Ошибка сохранения сессии:', err);
|
||
return res.status(500).json({ error: 'Ошибка сохранения сессии' });
|
||
}
|
||
|
||
console.log(`Успешная авторизация: ${user.name} (${user.login}) через ${user.auth_type}`);
|
||
if (user.groups) {
|
||
console.log(`Группы пользователя: ${user.groups}`);
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
user: sessionUser
|
||
});
|
||
});
|
||
} else {
|
||
console.log(`Неудачная попытка входа: ${login}`);
|
||
res.status(401).json({ error: 'Неверный логин или пароль' });
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка аутентификации:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера при авторизации' });
|
||
}
|
||
});
|
||
|
||
app.post('/api/logout', (req, res) => {
|
||
req.session.destroy((err) => {
|
||
if (err) {
|
||
console.error('Ошибка при выходе:', err);
|
||
return res.status(500).json({ error: 'Ошибка при выходе' });
|
||
}
|
||
res.json({ success: true });
|
||
});
|
||
});
|
||
|
||
app.get('/api/user', (req, res) => {
|
||
if (req.session.user) {
|
||
if (req.session.user.auth_type === 'ldap') {
|
||
db.get("SELECT groups FROM users WHERE id = ?", [req.session.user.id], (err, user) => {
|
||
if (err || !user) {
|
||
req.session.destroy();
|
||
return res.status(401).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
let groups = [];
|
||
try {
|
||
groups = JSON.parse(user.groups || '[]');
|
||
} catch (e) {
|
||
groups = [];
|
||
}
|
||
|
||
const allowedGroups = process.env.ALLOWED_GROUPS ?
|
||
process.env.ALLOWED_GROUPS.split(',').map(g => g.trim()) : [];
|
||
|
||
const isAdmin = groups.some(group => allowedGroups.includes(group));
|
||
const actualRole = isAdmin ? 'admin' : 'teacher';
|
||
|
||
if (req.session.user.role !== actualRole) {
|
||
console.log(`Обновлена роль пользователя ${req.session.user.login} с ${req.session.user.role} на ${actualRole}`);
|
||
|
||
db.run(
|
||
"UPDATE users SET role = ?, updated_at = datetime('now') WHERE id = ?",
|
||
[actualRole, req.session.user.id]
|
||
);
|
||
|
||
req.session.user.role = actualRole;
|
||
}
|
||
|
||
res.json({ user: req.session.user });
|
||
});
|
||
} else {
|
||
res.json({ user: req.session.user });
|
||
}
|
||
} else {
|
||
res.status(401).json({ error: 'Не аутентифицирован' });
|
||
}
|
||
});
|
||
|
||
app.get('/api/users', requireAuth, (req, res) => {
|
||
const search = req.query.search || '';
|
||
|
||
let query = `
|
||
SELECT id, login, name, email, role, auth_type
|
||
FROM users
|
||
WHERE role IN ('admin', 'teacher')
|
||
`;
|
||
|
||
const params = [];
|
||
|
||
if (search) {
|
||
query += ` AND (login LIKE ? OR name LIKE ? OR email LIKE ?)`;
|
||
const searchPattern = `%${search}%`;
|
||
params.push(searchPattern, searchPattern, searchPattern);
|
||
}
|
||
|
||
query += " ORDER BY name";
|
||
|
||
db.all(query, params, (err, rows) => {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
res.json(rows);
|
||
});
|
||
});
|
||
|
||
app.get('/api/tasks', requireAuth, (req, res) => {
|
||
const userId = req.session.user.id;
|
||
const showDeleted = req.session.user.role === 'admin' && req.query.showDeleted === 'true';
|
||
const search = req.query.search || '';
|
||
const statusFilter = req.query.status || 'active,in_progress,assigned,overdue,rework';
|
||
const creatorFilter = req.query.creator || '';
|
||
const assigneeFilter = req.query.assignee || '';
|
||
const deadlineFilter = req.query.deadline || '';
|
||
|
||
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 (req.session.user.role !== 'admin') {
|
||
query += ` AND (t.created_by = ? OR ta.user_id = ?)`;
|
||
params.push(userId, userId);
|
||
}
|
||
|
||
if (!showDeleted) {
|
||
query += " AND t.status = 'active'";
|
||
}
|
||
|
||
if (statusFilter && statusFilter !== 'all') {
|
||
const statuses = statusFilter.split(',');
|
||
|
||
if (statuses.includes('closed')) {
|
||
if (req.session.user.role !== 'admin') {
|
||
query += ` AND (t.closed_at IS NOT NULL AND t.created_by = ?)`;
|
||
params.push(userId);
|
||
} else {
|
||
query += ` AND t.closed_at IS NOT NULL`;
|
||
}
|
||
} else {
|
||
query += ` AND t.closed_at IS NULL`;
|
||
|
||
if (statuses.length > 0 && !statuses.includes('all')) {
|
||
query += ` AND EXISTS (
|
||
SELECT 1 FROM task_assignments ta2
|
||
WHERE ta2.task_id = t.id AND ta2.status IN (${statuses.map(() => '?').join(',')})
|
||
)`;
|
||
statuses.forEach(status => params.push(status));
|
||
}
|
||
}
|
||
} else {
|
||
if (req.session.user.role !== 'admin') {
|
||
query += ` AND (t.closed_at IS NULL OR t.created_by = ?)`;
|
||
params.push(userId);
|
||
}
|
||
}
|
||
|
||
if (creatorFilter) {
|
||
query += ` AND t.created_by = ?`;
|
||
params.push(creatorFilter);
|
||
}
|
||
|
||
if (assigneeFilter) {
|
||
query += ` AND ta.user_id = ?`;
|
||
params.push(assigneeFilter);
|
||
}
|
||
|
||
if (deadlineFilter) {
|
||
const now = new Date();
|
||
let hours = 48;
|
||
if (deadlineFilter === '24h') hours = 24;
|
||
|
||
const deadlineTime = new Date(now.getTime() + hours * 60 * 60 * 1000);
|
||
const deadlineISO = deadlineTime.toISOString();
|
||
const nowISO = now.toISOString();
|
||
|
||
query += ` AND ta.due_date IS NOT NULL
|
||
AND ta.due_date > ?
|
||
AND ta.due_date <= ?
|
||
AND ta.status NOT IN ('completed', 'overdue')`;
|
||
params.push(nowISO, deadlineISO);
|
||
}
|
||
|
||
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";
|
||
|
||
db.all(query, params, (err, tasks) => {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
const taskPromises = tasks.map(task => {
|
||
return 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, assignments) => {
|
||
if (err) {
|
||
task.assignments = [];
|
||
resolve(task);
|
||
return;
|
||
}
|
||
|
||
assignments.forEach(assignment => {
|
||
if (checkIfOverdue(assignment.due_date, assignment.status) && assignment.status !== 'completed') {
|
||
assignment.status = 'overdue';
|
||
}
|
||
});
|
||
|
||
task.assignments = assignments || [];
|
||
resolve(task);
|
||
});
|
||
});
|
||
});
|
||
|
||
Promise.all(taskPromises).then(completedTasks => {
|
||
res.json(completedTasks);
|
||
});
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks', requireAuth, upload.array('files', 15), (req, res) => {
|
||
const { title, description, assignedUsers, originalTaskId, dueDate } = req.body;
|
||
const createdBy = req.session.user.id;
|
||
|
||
if (!title) {
|
||
return res.status(400).json({ error: 'Название задачи обязательно' });
|
||
}
|
||
|
||
if (!dueDate) {
|
||
return res.status(400).json({ error: 'Дата и время выполнения обязательны' });
|
||
}
|
||
|
||
db.serialize(() => {
|
||
const startDate = new Date().toISOString();
|
||
|
||
db.run(
|
||
"INSERT INTO tasks (title, description, created_by, original_task_id, start_date, due_date) VALUES (?, ?, ?, ?, ?, ?)",
|
||
[title, description, createdBy, originalTaskId || null, startDate, dueDate || null],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
const taskId = this.lastID;
|
||
|
||
saveTaskMetadata(taskId, title, description, createdBy, originalTaskId, startDate, dueDate);
|
||
|
||
const action = originalTaskId ? 'TASK_COPIED' : 'TASK_CREATED';
|
||
const details = originalTaskId ?
|
||
`Создана копия задачи: ${title}` :
|
||
`Создана задача: ${title}`;
|
||
|
||
logActivity(taskId, createdBy, action, details);
|
||
|
||
if (req.files && req.files.length > 0) {
|
||
const userFolder = createUserTaskFolder(taskId, req.session.user.login);
|
||
|
||
req.files.forEach(file => {
|
||
const newPath = path.join(userFolder, path.basename(file.filename));
|
||
fs.renameSync(file.path, newPath);
|
||
|
||
db.run(
|
||
"INSERT INTO task_files (task_id, user_id, filename, original_name, file_path, file_size) VALUES (?, ?, ?, ?, ?, ?)",
|
||
[taskId, createdBy, path.basename(file.filename), file.originalname, newPath, file.size]
|
||
);
|
||
|
||
logActivity(taskId, createdBy, 'FILE_UPLOADED', `Загружен файл: ${file.originalname}`);
|
||
});
|
||
|
||
const tempDir = path.join(__dirname, 'data', 'uploads', 'temp');
|
||
if (fs.existsSync(tempDir)) {
|
||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||
}
|
||
}
|
||
|
||
if (assignedUsers) {
|
||
const userIds = Array.isArray(assignedUsers) ? assignedUsers : [assignedUsers];
|
||
|
||
userIds.forEach(userId => {
|
||
db.run(
|
||
"INSERT INTO task_assignments (task_id, user_id, start_date, due_date) VALUES (?, ?, ?, ?)",
|
||
[taskId, userId, startDate, dueDate || null]
|
||
);
|
||
|
||
logActivity(taskId, createdBy, 'TASK_ASSIGNED', `Задача назначена пользователю ${userId}`);
|
||
});
|
||
|
||
sendTaskNotifications('created', taskId, title, description, createdBy);
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
taskId: taskId,
|
||
message: originalTaskId ? 'Копия задачи создана' : 'Задача успешно создана'
|
||
});
|
||
}
|
||
);
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks/:taskId/copy', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const { assignedUsers, dueDate } = req.body;
|
||
const createdBy = req.session.user.id;
|
||
|
||
if (!dueDate) {
|
||
return res.status(400).json({ error: 'Дата и время выполнения обязательны для копии задачи' });
|
||
}
|
||
|
||
checkTaskAccess(createdBy, taskId, (err, hasAccess) => {
|
||
if (err || !hasAccess) {
|
||
return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' });
|
||
}
|
||
|
||
db.serialize(() => {
|
||
db.get("SELECT title, description FROM tasks WHERE id = ?", [taskId], (err, originalTask) => {
|
||
if (err || !originalTask) {
|
||
return res.status(404).json({ error: 'Оригинальная задача не найдена' });
|
||
}
|
||
|
||
const newTitle = `Копия: ${originalTask.title}`;
|
||
const startDate = new Date().toISOString();
|
||
|
||
db.run(
|
||
"INSERT INTO tasks (title, description, created_by, original_task_id, start_date, due_date) VALUES (?, ?, ?, ?, ?, ?)",
|
||
[newTitle, originalTask.description, createdBy, taskId, startDate, dueDate || null],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
const newTaskId = this.lastID;
|
||
|
||
saveTaskMetadata(newTaskId, newTitle, originalTask.description, createdBy, taskId, startDate, dueDate);
|
||
|
||
logActivity(newTaskId, createdBy, 'TASK_COPIED', `Создана копия задачи: ${newTitle}`);
|
||
|
||
db.all("SELECT * FROM task_files WHERE task_id = ?", [taskId], (err, originalFiles) => {
|
||
if (!err && originalFiles && originalFiles.length > 0) {
|
||
originalFiles.forEach(originalFile => {
|
||
const originalFilePath = originalFile.file_path;
|
||
const newFilename = Date.now() + '-' + Math.round(Math.random() * 1E9) + path.extname(originalFile.original_name);
|
||
const userFolder = createUserTaskFolder(newTaskId, req.session.user.login);
|
||
const newFilePath = path.join(userFolder, newFilename);
|
||
|
||
if (fs.existsSync(originalFilePath)) {
|
||
fs.copyFileSync(originalFilePath, newFilePath);
|
||
|
||
db.run(
|
||
"INSERT INTO task_files (task_id, user_id, filename, original_name, file_path, file_size) VALUES (?, ?, ?, ?, ?, ?)",
|
||
[newTaskId, createdBy, newFilename, originalFile.original_name, newFilePath, originalFile.file_size]
|
||
);
|
||
|
||
logActivity(newTaskId, createdBy, 'FILE_COPIED', `Скопирован файл: ${originalFile.original_name}`);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
if (assignedUsers && assignedUsers.length > 0) {
|
||
assignedUsers.forEach(userId => {
|
||
db.run(
|
||
"INSERT INTO task_assignments (task_id, user_id, start_date, due_date) VALUES (?, ?, ?, ?)",
|
||
[newTaskId, userId, startDate, dueDate || null]
|
||
);
|
||
});
|
||
|
||
logActivity(newTaskId, createdBy, 'TASK_ASSIGNED', `Задача назначена пользователям: ${assignedUsers.join(', ')}`);
|
||
|
||
sendTaskNotifications('created', newTaskId, newTitle, originalTask.description, createdBy);
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
taskId: newTaskId,
|
||
message: 'Копия задачи успешно создана'
|
||
});
|
||
}
|
||
);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
app.put('/api/tasks/:taskId', requireAuth, upload.array('files', 15), (req, res) => {
|
||
const { taskId } = req.params;
|
||
const { title, description, assignedUsers, dueDate } = req.body;
|
||
const userId = req.session.user.id;
|
||
|
||
if (!title) {
|
||
return res.status(400).json({ error: 'Название задачи обязательно' });
|
||
}
|
||
|
||
if (!dueDate) {
|
||
return res.status(400).json({ error: 'Дата и время выполнения обязательны' });
|
||
}
|
||
|
||
db.get("SELECT created_by FROM tasks WHERE id = ? AND status = 'active'", [taskId], (err, task) => {
|
||
if (err || !task) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
if (req.session.user.role !== 'admin' && task.created_by !== userId) {
|
||
return res.status(403).json({ error: 'У вас нет прав для редактирования этой задачи' });
|
||
}
|
||
|
||
db.serialize(() => {
|
||
db.run(
|
||
"UPDATE tasks SET title = ?, description = ?, due_date = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||
[title, description, dueDate || null, taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
updateTaskMetadata(taskId, { title, description, due_date: dueDate });
|
||
|
||
logActivity(taskId, userId, 'TASK_UPDATED', `Задача обновлена: ${title}`);
|
||
|
||
if (req.files && req.files.length > 0) {
|
||
const userFolder = createUserTaskFolder(taskId, req.session.user.login);
|
||
|
||
req.files.forEach(file => {
|
||
const newPath = path.join(userFolder, path.basename(file.filename));
|
||
fs.renameSync(file.path, newPath);
|
||
|
||
db.run(
|
||
"INSERT INTO task_files (task_id, user_id, filename, original_name, file_path, file_size) VALUES (?, ?, ?, ?, ?, ?)",
|
||
[taskId, userId, path.basename(file.filename), file.originalname, newPath, file.size]
|
||
);
|
||
|
||
logActivity(taskId, userId, 'FILE_UPLOADED', `Загружен файл: ${file.originalname}`);
|
||
});
|
||
|
||
const tempDir = path.join(__dirname, 'data', 'uploads', 'temp');
|
||
if (fs.existsSync(tempDir)) {
|
||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||
}
|
||
}
|
||
|
||
if (assignedUsers) {
|
||
db.run("DELETE FROM task_assignments WHERE task_id = ?", [taskId], (err) => {
|
||
if (err) {
|
||
console.error('Ошибка удаления старых назначений:', err);
|
||
}
|
||
|
||
const userIds = Array.isArray(assignedUsers) ? assignedUsers : [assignedUsers];
|
||
userIds.forEach(userId => {
|
||
const startDate = new Date().toISOString();
|
||
db.run(
|
||
"INSERT INTO task_assignments (task_id, user_id, start_date, due_date) VALUES (?, ?, ?, ?)",
|
||
[taskId, userId, startDate, dueDate || null]
|
||
);
|
||
});
|
||
|
||
logActivity(taskId, userId, 'TASK_ASSIGNMENTS_UPDATED', `Назначения обновлены`);
|
||
|
||
sendTaskNotifications('updated', taskId, title, description, userId);
|
||
});
|
||
} else {
|
||
sendTaskNotifications('updated', taskId, title, description, userId);
|
||
}
|
||
|
||
res.json({ success: true, message: 'Задача обновлена' });
|
||
}
|
||
);
|
||
});
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks/:taskId/rework', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const { comment } = req.body;
|
||
const userId = req.session.user.id;
|
||
|
||
db.get("SELECT created_by FROM tasks WHERE id = ?", [taskId], (err, task) => {
|
||
if (err || !task) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
if (req.session.user.role !== 'admin' && task.created_by !== userId) {
|
||
return res.status(403).json({ error: 'У вас нет прав для возврата задачи на доработку' });
|
||
}
|
||
|
||
db.serialize(() => {
|
||
db.run(
|
||
"UPDATE tasks SET rework_comment = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||
[comment || 'Требуется доработка', taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
db.run(
|
||
"UPDATE task_assignments SET status = 'rework', rework_comment = ?, updated_at = CURRENT_TIMESTAMP WHERE task_id = ?",
|
||
[comment || 'Требуется доработка', taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
logActivity(taskId, userId, 'TASK_SENT_FOR_REWORK', `Задача возвращена на доработку: ${comment}`);
|
||
|
||
db.get("SELECT title, description FROM tasks WHERE id = ?", [taskId], (err, taskData) => {
|
||
if (!err && taskData) {
|
||
sendTaskNotifications('rework', taskId, taskData.title, taskData.description, userId, comment);
|
||
}
|
||
});
|
||
|
||
res.json({ success: true, message: 'Задача возвращена на доработку' });
|
||
}
|
||
);
|
||
}
|
||
);
|
||
});
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks/:taskId/close', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
db.get("SELECT created_by FROM tasks WHERE id = ?", [taskId], (err, task) => {
|
||
if (err || !task) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
if (req.session.user.role !== 'admin' && task.created_by !== userId) {
|
||
return res.status(403).json({ error: 'У вас нет прав для закрытия этой задачи' });
|
||
}
|
||
|
||
db.run(
|
||
"UPDATE tasks SET closed_at = CURRENT_TIMESTAMP, closed_by = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||
[userId, taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
logActivity(taskId, userId, 'TASK_CLOSED', `Задача закрыта`);
|
||
|
||
db.get("SELECT title FROM tasks WHERE id = ?", [taskId], (err, taskData) => {
|
||
if (!err && taskData) {
|
||
sendTaskNotifications('closed', taskId, taskData.title, '', userId);
|
||
}
|
||
});
|
||
|
||
res.json({ success: true, message: 'Задача закрыта' });
|
||
}
|
||
);
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks/:taskId/reopen', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
db.get("SELECT created_by FROM tasks WHERE id = ?", [taskId], (err, task) => {
|
||
if (err || !task) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
if (req.session.user.role !== 'admin' && task.created_by !== userId) {
|
||
return res.status(403).json({ error: 'У вас нет прав для открытия этой задачи' });
|
||
}
|
||
|
||
db.run(
|
||
"UPDATE tasks SET closed_at = NULL, closed_by = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||
[taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
logActivity(taskId, userId, 'TASK_REOPENED', `Задача открыта`);
|
||
res.json({ success: true, message: 'Задача открыта' });
|
||
}
|
||
);
|
||
});
|
||
});
|
||
|
||
app.put('/api/tasks/:taskId/assignment/:userId', requireAuth, (req, res) => {
|
||
const { taskId, userId } = req.params;
|
||
const { dueDate } = req.body;
|
||
const currentUserId = req.session.user.id;
|
||
|
||
if (!dueDate) {
|
||
return res.status(400).json({ error: 'Дата и время выполнения обязательны' });
|
||
}
|
||
|
||
db.get("SELECT created_by FROM tasks WHERE id = ?", [taskId], (err, task) => {
|
||
if (err || !task) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
if (req.session.user.role !== 'admin' && task.created_by !== currentUserId) {
|
||
return res.status(403).json({ error: 'У вас нет прав для редактирования сроки' });
|
||
}
|
||
|
||
db.run(
|
||
"UPDATE task_assignments SET due_date = ?, updated_at = CURRENT_TIMESTAMP WHERE task_id = ? AND user_id = ?",
|
||
[dueDate || null, taskId, userId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: 'Назначение не найдено' });
|
||
}
|
||
|
||
logActivity(taskId, currentUserId, 'ASSIGNMENT_UPDATED', `Обновлены сроки для пользователя ${userId}`);
|
||
|
||
db.get("SELECT title, description FROM tasks WHERE id = ?", [taskId], (err, taskData) => {
|
||
if (!err && taskData) {
|
||
sendTaskNotifications('updated', taskId, taskData.title, taskData.description, currentUserId);
|
||
}
|
||
});
|
||
|
||
res.json({ success: true, message: 'Сроки обновлены' });
|
||
}
|
||
);
|
||
});
|
||
});
|
||
|
||
app.delete('/api/tasks/:taskId', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
db.get("SELECT created_by FROM tasks WHERE id = ? AND status = 'active'", [taskId], (err, task) => {
|
||
if (err || !task) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
if (req.session.user.role !== 'admin' && task.created_by !== userId) {
|
||
return res.status(403).json({ error: 'У вас нет прав для удаления этой задачи' });
|
||
}
|
||
|
||
db.run(
|
||
"UPDATE tasks SET status = 'deleted', deleted_at = CURRENT_TIMESTAMP, deleted_by = ? WHERE id = ?",
|
||
[userId, taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
updateTaskMetadata(taskId, {
|
||
status: 'deleted',
|
||
deleted_at: new Date().toISOString(),
|
||
deleted_by: userId
|
||
});
|
||
|
||
logActivity(taskId, userId, 'TASK_DELETED', `Задача помечена как удаленная`);
|
||
|
||
res.json({ success: true, message: 'Задача удалена' });
|
||
}
|
||
);
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks/:taskId/restore', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
if (req.session.user.role !== 'admin') {
|
||
return res.status(403).json({ error: 'Недостаточно прав' });
|
||
}
|
||
|
||
db.run(
|
||
"UPDATE tasks SET status = 'active', deleted_at = NULL, deleted_by = NULL WHERE id = ?",
|
||
[taskId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: 'Задача не найдена' });
|
||
}
|
||
|
||
updateTaskMetadata(taskId, {
|
||
status: 'active',
|
||
deleted_at: null,
|
||
deleted_by: null
|
||
});
|
||
|
||
logActivity(taskId, userId, 'TASK_RESTORED', `Задача восстановлена`);
|
||
|
||
res.json({ success: true, message: 'Задача восстановлена' });
|
||
}
|
||
);
|
||
});
|
||
|
||
app.put('/api/tasks/:taskId/status', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const { userId: targetUserId, status } = req.body;
|
||
const currentUserId = req.session.user.id;
|
||
|
||
if (parseInt(targetUserId) !== currentUserId) {
|
||
return res.status(403).json({ error: 'Недостаточно прав' });
|
||
}
|
||
|
||
if (!targetUserId || !status) {
|
||
return res.status(400).json({ error: 'userId и status обязательны' });
|
||
}
|
||
|
||
db.get("SELECT 1 FROM task_assignments WHERE task_id = ? AND user_id = ?", [taskId, currentUserId], (err, assignment) => {
|
||
if (err || !assignment) {
|
||
return res.status(403).json({ error: 'Вы не назначены на эту задачу' });
|
||
}
|
||
|
||
db.get(`
|
||
SELECT t.title, t.description, u.name as user_name
|
||
FROM tasks t
|
||
LEFT JOIN users u ON u.id = ?
|
||
WHERE t.id = ?
|
||
`, [currentUserId, taskId], (err, taskData) => {
|
||
if (err) {
|
||
console.error('Ошибка получения данных задачи:', err);
|
||
}
|
||
|
||
const finalStatus = status === 'completed' ? 'completed' : status;
|
||
|
||
db.run(
|
||
"UPDATE task_assignments SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE task_id = ? AND user_id = ?",
|
||
[finalStatus, taskId, targetUserId],
|
||
function(err) {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
res.status(404).json({ error: 'Назначение не найдено' });
|
||
return;
|
||
}
|
||
|
||
logActivity(taskId, targetUserId, 'STATUS_CHANGED', `Статус изменен на: ${finalStatus}`);
|
||
|
||
if (taskData) {
|
||
sendTaskNotifications(
|
||
'status_changed',
|
||
taskId,
|
||
taskData.title,
|
||
taskData.description,
|
||
currentUserId,
|
||
'',
|
||
finalStatus,
|
||
taskData.user_name || req.session.user.name
|
||
);
|
||
}
|
||
|
||
res.json({ success: true, message: 'Статус обновлен' });
|
||
}
|
||
);
|
||
});
|
||
});
|
||
});
|
||
|
||
app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (req, res) => {
|
||
const { taskId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
if (!req.files || req.files.length === 0) {
|
||
return res.status(400).json({ error: 'Нет файлов для загрузки' });
|
||
}
|
||
|
||
checkTaskAccess(userId, taskId, (err, hasAccess) => {
|
||
if (err || !hasAccess) {
|
||
return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' });
|
||
}
|
||
|
||
req.files.forEach(file => {
|
||
db.run(
|
||
"INSERT INTO task_files (task_id, user_id, filename, original_name, file_path, file_size) VALUES (?, ?, ?, ?, ?, ?)",
|
||
[taskId, userId, path.basename(file.filename), file.originalname, file.path, file.size]
|
||
);
|
||
|
||
logActivity(taskId, userId, 'FILE_UPLOADED', `Загружен файл: ${file.originalname}`);
|
||
});
|
||
|
||
res.json({ success: true, message: 'Файлы успешно загружены' });
|
||
});
|
||
});
|
||
|
||
app.get('/api/tasks/:taskId/files', requireAuth, (req, res) => {
|
||
const { taskId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
checkTaskAccess(userId, taskId, (err, hasAccess) => {
|
||
if (err || !hasAccess) {
|
||
return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' });
|
||
}
|
||
|
||
db.all(`
|
||
SELECT tf.*, u.name as user_name, u.login as user_login
|
||
FROM task_files tf
|
||
LEFT JOIN users u ON tf.user_id = u.id
|
||
WHERE tf.task_id = ?
|
||
ORDER BY tf.uploaded_at DESC
|
||
`, [taskId], (err, files) => {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
res.json(files);
|
||
});
|
||
});
|
||
});
|
||
|
||
app.get('/api/files/:fileId/download', requireAuth, (req, res) => {
|
||
const { fileId } = req.params;
|
||
const userId = req.session.user.id;
|
||
|
||
db.get("SELECT tf.*, t.id as task_id FROM task_files tf JOIN tasks t ON tf.task_id = t.id WHERE tf.id = ?", [fileId], (err, file) => {
|
||
if (err || !file) {
|
||
return res.status(404).json({ error: 'Файл не найдена' });
|
||
}
|
||
|
||
checkTaskAccess(userId, file.task_id, (err, hasAccess) => {
|
||
if (err || !hasAccess) {
|
||
return res.status(404).json({ error: 'Файл не найден или у вас нет прав доступа' });
|
||
}
|
||
|
||
if (!fs.existsSync(file.file_path)) {
|
||
return res.status(404).json({ error: 'Файл не найден на сервере' });
|
||
}
|
||
|
||
res.download(file.file_path, file.original_name);
|
||
});
|
||
});
|
||
});
|
||
|
||
app.get('/api/activity-logs', requireAuth, (req, res) => {
|
||
const userId = req.session.user.id;
|
||
|
||
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
|
||
`;
|
||
|
||
if (req.session.user.role !== 'admin') {
|
||
query += ` AND (t.created_by = ${userId} OR al.task_id IN (
|
||
SELECT task_id FROM task_assignments WHERE user_id = ${userId}
|
||
))`;
|
||
}
|
||
|
||
query += " ORDER BY al.created_at DESC LIMIT 100";
|
||
|
||
db.all(query, (err, logs) => {
|
||
if (err) {
|
||
res.status(500).json({ error: err.message });
|
||
return;
|
||
}
|
||
res.json(logs);
|
||
});
|
||
});
|
||
app.get('/admin', (req, res) => {
|
||
if (!req.session.user || req.session.user.role !== 'admin') {
|
||
return res.status(403).send('Доступ запрещен');
|
||
}
|
||
res.sendFile(path.join(__dirname, 'public/admin.html'));
|
||
});
|
||
app.listen(PORT, () => {
|
||
console.log(`CRM сервер запущен на порту ${PORT}`);
|
||
console.log(`Откройте http://localhost:${PORT} в браузере`);
|
||
console.log('Данные хранятся в папке:', path.join(__dirname, 'data'));
|
||
console.log('Тестовые пользователи:');
|
||
console.log('- Логин: director, Пароль: director123 (Администратор)');
|
||
console.log('- Логин: zavuch, Пароль: zavuch123');
|
||
console.log('- Логин: teacher, Пароль: teacher123');
|
||
console.log('LDAP авторизация доступна для пользователей школы');
|
||
console.log(`Разрешенные группы: ${process.env.ALLOWED_GROUPS}`);
|
||
console.log('Система уведомлений активна');
|
||
|
||
setInterval(checkOverdueTasks, 60000);
|
||
setInterval(checkUpcomingDeadlines, 60000);
|
||
}); |