upravlenie-service
This commit is contained in:
@@ -126,63 +126,66 @@ class UpravlenieService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание нового подключения
|
||||
*/
|
||||
async createConnection(data) {
|
||||
const {
|
||||
service_id, service_name, service_type, login, password,
|
||||
api_url, local_user_id, local_user_login, sync_direction = 'outgoing',
|
||||
sync_enabled = 1, sync_interval = 60
|
||||
} = data;
|
||||
/**
|
||||
* Создание нового подключения
|
||||
*/
|
||||
async createConnection(data) {
|
||||
const {
|
||||
service_id, service_name, service_type, login, password,
|
||||
api_url, local_user_id, local_user_login, sync_direction = 'outgoing',
|
||||
sync_enabled = 1, sync_interval = 60
|
||||
} = data;
|
||||
|
||||
// Валидация service_id
|
||||
if (service_id < SERVICE_ID_RANGE.min || service_id > SERVICE_ID_RANGE.max) {
|
||||
throw new Error(`service_id должен быть в диапазоне ${SERVICE_ID_RANGE.min}-${SERVICE_ID_RANGE.max}`);
|
||||
}
|
||||
|
||||
// Для организатора api_url может быть пустым
|
||||
if (service_type === 'executor' && !api_url) {
|
||||
throw new Error('Для исполнителя необходимо указать api_url организатора');
|
||||
}
|
||||
|
||||
// Проверяем уникальность service_id для активных подключений
|
||||
const existing = await this.getConnectionByServiceId(service_id);
|
||||
if (existing && existing.is_active) {
|
||||
throw new Error(`Подключение с service_id ${service_id} уже существует`);
|
||||
}
|
||||
|
||||
const sql = `
|
||||
INSERT INTO upravlenie (
|
||||
service_id, service_name, service_type, login, password,
|
||||
api_url, local_user_id, local_user_login, sync_direction,
|
||||
sync_enabled, sync_interval
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [
|
||||
service_id, service_name, service_type, login, password,
|
||||
api_url, local_user_id || null, local_user_login || null,
|
||||
sync_direction, sync_enabled, sync_interval
|
||||
], function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const connectionId = this.lastID;
|
||||
console.log(`✅ Создано подключение ${service_name} (ID: ${connectionId}, service_id: ${service_id})`);
|
||||
|
||||
// Запускаем синхронизацию для нового подключения
|
||||
if (sync_enabled) {
|
||||
this.startSyncJob(connectionId, sync_interval);
|
||||
}
|
||||
|
||||
resolve({ id: connectionId, ...data });
|
||||
}
|
||||
});
|
||||
});
|
||||
// Валидация service_id
|
||||
if (service_id < SERVICE_ID_RANGE.min || service_id > SERVICE_ID_RANGE.max) {
|
||||
throw new Error(`service_id должен быть в диапазоне ${SERVICE_ID_RANGE.min}-${SERVICE_ID_RANGE.max}`);
|
||||
}
|
||||
|
||||
// Для организатора api_url может быть пустым
|
||||
if (service_type === 'executor' && !api_url) {
|
||||
throw new Error('Для исполнителя необходимо указать api_url организатора');
|
||||
}
|
||||
|
||||
// Проверяем существование локального пользователя если указан
|
||||
if (local_user_id) {
|
||||
const userExists = await this.checkLocalUserExists(local_user_id);
|
||||
if (!userExists) {
|
||||
throw new Error(`Пользователь с ID ${local_user_id} не существует`);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем уникальность service_id для активных подключений
|
||||
const existing = await this.getConnectionByServiceId(service_id);
|
||||
if (existing && existing.is_active) {
|
||||
throw new Error(`Подключение с service_id ${service_id} уже существует`);
|
||||
}
|
||||
|
||||
const sql = `
|
||||
INSERT INTO upravlenie (
|
||||
service_id, service_name, service_type, login, password,
|
||||
api_url, local_user_id, local_user_login, sync_direction,
|
||||
sync_enabled, sync_interval
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [
|
||||
service_id, service_name, service_type, login, password,
|
||||
api_url, local_user_id || null, local_user_login || null,
|
||||
sync_direction, sync_enabled, sync_interval
|
||||
], function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const connectionId = this.lastID;
|
||||
console.log(`✅ Создано подключение ${service_name} (ID: ${connectionId}, service_id: ${service_id})`);
|
||||
|
||||
resolve({ id: connectionId, ...data });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение подключения по ID
|
||||
*/
|
||||
@@ -279,12 +282,6 @@ class UpravlenieService {
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`✅ Подключение ${id} обновлено`);
|
||||
|
||||
// Перезапускаем задачу синхронизации если изменились настройки
|
||||
if (data.sync_enabled !== undefined || data.sync_interval !== undefined) {
|
||||
this.restartSyncJob(id);
|
||||
}
|
||||
|
||||
resolve({ id, changes: this.changes });
|
||||
}
|
||||
});
|
||||
@@ -307,9 +304,9 @@ class UpravlenieService {
|
||||
sync_enabled: true
|
||||
});
|
||||
|
||||
connections.forEach(conn => {
|
||||
for (const conn of connections) {
|
||||
this.startSyncJob(conn.id, conn.sync_interval);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ Запущено ${connections.length} задач синхронизации`);
|
||||
}
|
||||
@@ -419,52 +416,86 @@ class UpravlenieService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Синхронизация от исполнителя к организатору
|
||||
*/
|
||||
async syncToOrganizer(connection) {
|
||||
if (!connection.api_url) {
|
||||
throw new Error('Для исполнителя не указан api_url организатора');
|
||||
}
|
||||
|
||||
// Получаем задачи, назначенные локальному пользователю
|
||||
const localTasks = await this.getTasksForLocalUser(connection.local_user_id);
|
||||
|
||||
// Получаем задачи от организатора
|
||||
const organizerTasks = await this.fetchTasksFromOrganizer(connection);
|
||||
|
||||
// Обновляем локальные задачи
|
||||
await this.syncTasksWithOrganizer(connection, organizerTasks, localTasks);
|
||||
|
||||
// Отправляем обновленные статусы организатору
|
||||
await this.sendTaskStatusesToOrganizer(connection, localTasks);
|
||||
/**
|
||||
* Синхронизация от исполнителя к организатору
|
||||
*/
|
||||
async syncToOrganizer(connection) {
|
||||
if (!connection.api_url) {
|
||||
throw new Error('Для исполнителя не указан api_url организатора');
|
||||
}
|
||||
|
||||
/**
|
||||
* Синхронизация от организатора к исполнителям
|
||||
*/
|
||||
async syncFromExecutors(connection) {
|
||||
// Получаем все активные подключения исполнителей с таким же service_id
|
||||
const executors = await this.getAllConnections({
|
||||
service_type: 'executor',
|
||||
is_active: true,
|
||||
sync_enabled: true
|
||||
});
|
||||
// Проверяем наличие локального пользователя
|
||||
if (!connection.local_user_id) {
|
||||
console.warn(`⚠️ Для подключения ${connection.service_name} не указан локальный пользователь. Задачи не будут создаваться.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const executor of executors) {
|
||||
try {
|
||||
// Получаем статусы задач от исполнителя
|
||||
const taskStatuses = await this.fetchTaskStatusesFromExecutor(executor);
|
||||
// Получаем задачи, назначенные локальному пользователю
|
||||
const localTasks = await this.getTasksForLocalUser(connection.local_user_id);
|
||||
|
||||
// Получаем задачи от организатора
|
||||
const organizerTasks = await this.fetchTasksFromOrganizer(connection);
|
||||
|
||||
if (organizerTasks.length === 0) {
|
||||
console.log(`📭 Нет новых задач от организатора для ${connection.service_name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Обновляем локальные задачи
|
||||
await this.syncTasksWithOrganizer(connection, organizerTasks, localTasks);
|
||||
|
||||
// Отправляем обновленные статусы организатору
|
||||
await this.sendTaskStatusesToOrganizer(connection, localTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Синхронизация от организатора к исполнителям
|
||||
*/
|
||||
async syncFromExecutors(connection) {
|
||||
// Получаем все активные подключения исполнителей с таким же service_id
|
||||
const executors = await this.getAllConnections({
|
||||
service_type: 'executor',
|
||||
is_active: true,
|
||||
sync_enabled: true
|
||||
});
|
||||
|
||||
if (executors.length === 0) {
|
||||
console.log(`📭 Нет активных исполнителей для организатора ${connection.service_name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const executor of executors) {
|
||||
try {
|
||||
// Проверяем наличие локального пользователя у исполнителя
|
||||
if (!executor.local_user_id) {
|
||||
console.warn(`⚠️ Для исполнителя ${executor.service_name} не указан локальный пользователь. Пропускаем...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Получаем статусы задач от исполнителя
|
||||
const taskStatuses = await this.fetchTaskStatusesFromExecutor(executor);
|
||||
|
||||
if (taskStatuses.length > 0) {
|
||||
// Обновляем статусы в локальной БД
|
||||
await this.updateTaskStatusesFromExecutor(executor, taskStatuses);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Ошибка получения статусов от исполнителя ${executor.service_name}:`, error.message);
|
||||
console.log(`✅ Получено ${taskStatuses.length} статусов от исполнителя ${executor.service_name}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Ошибка получения статусов от исполнителя ${executor.service_name}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Проверка существования локального пользователя
|
||||
*/
|
||||
async checkLocalUserExists(userId) {
|
||||
return new Promise((resolve) => {
|
||||
this.db.get('SELECT id FROM users WHERE id = ?', [userId], (err, row) => {
|
||||
resolve(!!row);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Получение задач для локального пользователя
|
||||
*/
|
||||
@@ -582,62 +613,99 @@ class UpravlenieService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание задачи из данных организатора
|
||||
*/
|
||||
async createTaskFromOrganizer(connection, taskData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.serialize(() => {
|
||||
// Создаем задачу
|
||||
// upravlenie-service.js (исправленная версия метода createTaskFromOrganizer)
|
||||
|
||||
/**
|
||||
* Создание задачи из данных организатора
|
||||
*/
|
||||
async createTaskFromOrganizer(connection, taskData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Проверяем, что local_user_id не NULL
|
||||
if (!connection.local_user_id) {
|
||||
reject(new Error('local_user_id не может быть пустым. Укажите локального пользователя для создания задач.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Сначала проверяем структуру таблицы tasks
|
||||
this.db.all("PRAGMA table_info(tasks)", (err, columns) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const columnNames = columns.map(c => c.name);
|
||||
|
||||
// Формируем SQL запрос динамически
|
||||
let fields = ['title', 'description', 'status', 'created_by', 'start_date', 'due_date', 'task_type'];
|
||||
let placeholders = ['?', '?', '?', '?', '?', '?', '?'];
|
||||
let values = [
|
||||
taskData.title,
|
||||
taskData.description || '',
|
||||
'active',
|
||||
connection.local_user_id, // теперь точно не NULL
|
||||
taskData.start_date || new Date().toISOString(),
|
||||
taskData.due_date || null,
|
||||
'external'
|
||||
];
|
||||
|
||||
// Добавляем external_task_id если колонка существует
|
||||
if (columnNames.includes('external_task_id')) {
|
||||
fields.push('external_task_id');
|
||||
placeholders.push('?');
|
||||
values.push(taskData.id);
|
||||
}
|
||||
|
||||
// Добавляем external_service_id если колонка существует
|
||||
if (columnNames.includes('external_service_id')) {
|
||||
fields.push('external_service_id');
|
||||
placeholders.push('?');
|
||||
values.push(connection.service_id);
|
||||
}
|
||||
|
||||
const sql = `INSERT INTO tasks (${fields.join(', ')}) VALUES (${placeholders.join(', ')})`;
|
||||
|
||||
console.log(`📝 Создание задачи от организатора:`, {
|
||||
sql,
|
||||
values,
|
||||
local_user_id: connection.local_user_id,
|
||||
service_id: connection.service_id
|
||||
});
|
||||
|
||||
this.db.run(sql, values, function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка создания задачи:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const newTaskId = this.lastID;
|
||||
|
||||
// Назначаем задачу локальному пользователю
|
||||
this.db.run(
|
||||
`INSERT INTO tasks (
|
||||
title, description, status, created_by,
|
||||
external_task_id, external_service_id, start_date, due_date, task_type
|
||||
) VALUES (?, ?, 'active', ?, ?, ?, ?, ?, 'external')`,
|
||||
`INSERT INTO task_assignments (task_id, user_id, status, start_date, due_date)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
taskData.title,
|
||||
taskData.description || '',
|
||||
newTaskId,
|
||||
connection.local_user_id,
|
||||
taskData.id,
|
||||
connection.service_id,
|
||||
taskData.status || 'assigned',
|
||||
taskData.start_date || new Date().toISOString(),
|
||||
taskData.due_date || null
|
||||
],
|
||||
function(err) {
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка назначения задачи:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const newTaskId = this.lastID;
|
||||
|
||||
// Назначаем задачу локальному пользователю
|
||||
this.db.run(
|
||||
`INSERT INTO task_assignments (task_id, user_id, status, start_date, due_date)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
newTaskId,
|
||||
connection.local_user_id,
|
||||
taskData.status || 'assigned',
|
||||
taskData.start_date || new Date().toISOString(),
|
||||
taskData.due_date || null
|
||||
],
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Создана задача ${newTaskId} от организатора ${connection.service_name}`);
|
||||
resolve(newTaskId);
|
||||
}
|
||||
);
|
||||
console.log(`✅ Создана задача ${newTaskId} от организатора ${connection.service_name}`);
|
||||
resolve(newTaskId);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Обновление задачи из данных организатора
|
||||
*/
|
||||
@@ -668,30 +736,65 @@ class UpravlenieService {
|
||||
this.db.run(
|
||||
`UPDATE task_assignments
|
||||
SET status = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE task_id = (
|
||||
SELECT id FROM tasks
|
||||
WHERE external_task_id = ? AND external_service_id = ?
|
||||
) AND user_id = ?`,
|
||||
WHERE task_id = ? AND user_id = ?`,
|
||||
[
|
||||
status.status,
|
||||
status.external_task_id,
|
||||
executor.service_id,
|
||||
status.task_id,
|
||||
executor.local_user_id
|
||||
],
|
||||
function(err) {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
if (this.changes > 0) {
|
||||
console.log(`✅ Обновлен статус задачи ${status.external_task_id}: ${status.status}`);
|
||||
console.log(`✅ Обновлен статус задачи ${status.task_id}: ${status.status}`);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Если статус "completed" - проверяем и закрываем задачу если все выполнили
|
||||
if (status.status === 'completed') {
|
||||
await this.checkAndCloseTaskIfAllCompleted(status.task_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка и закрытие задачи если все исполнители выполнили
|
||||
*/
|
||||
async checkAndCloseTaskIfAllCompleted(taskId) {
|
||||
return new Promise((resolve) => {
|
||||
this.db.all(
|
||||
'SELECT status FROM task_assignments WHERE task_id = ?',
|
||||
[taskId],
|
||||
(err, assignments) => {
|
||||
if (!err && assignments && assignments.length > 0) {
|
||||
const allCompleted = assignments.every(a => a.status === 'completed');
|
||||
|
||||
if (allCompleted) {
|
||||
this.db.run(
|
||||
'UPDATE tasks SET closed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[taskId],
|
||||
(err) => {
|
||||
if (!err) {
|
||||
console.log(`✅ Задача ${taskId} автоматически закрыта`);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка файла в удаленный сервис
|
||||
*/
|
||||
@@ -767,7 +870,7 @@ class UpravlenieService {
|
||||
|
||||
// Сохраняем файл локально
|
||||
const { createUserTaskFolder } = require('./database');
|
||||
const userFolder = createUserTaskFolder(localTaskId, connection.local_user_login);
|
||||
const userFolder = createUserTaskFolder(localTaskId, connection.local_user_login || 'external');
|
||||
|
||||
const fileName = response.headers['x-file-name'] || `remote_${Date.now()}.bin`;
|
||||
const filePath = path.join(userFolder, fileName);
|
||||
@@ -904,7 +1007,7 @@ function setupUpravlenieEndpoints(app, db) {
|
||||
|
||||
// Middleware для проверки аутентификации
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
if (!req.session || !req.session.user) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
next();
|
||||
@@ -912,7 +1015,10 @@ function setupUpravlenieEndpoints(app, db) {
|
||||
|
||||
// Middleware для проверки прав администратора
|
||||
const requireAdmin = (req, res, next) => {
|
||||
if (!req.session.user || req.session.user.role !== 'admin') {
|
||||
if (!req.session || !req.session.user) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
if (req.session.user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Требуются права администратора' });
|
||||
}
|
||||
next();
|
||||
@@ -966,7 +1072,7 @@ function setupUpravlenieEndpoints(app, db) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Подключение создано',
|
||||
connection
|
||||
connection: connection
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка создания подключения:', error);
|
||||
@@ -1066,6 +1172,7 @@ function setupUpravlenieEndpoints(app, db) {
|
||||
req.externalConnection = connection;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка аутентификации:', error);
|
||||
res.status(500).json({ error: 'Ошибка аутентификации' });
|
||||
}
|
||||
};
|
||||
@@ -1178,7 +1285,7 @@ function setupUpravlenieEndpoints(app, db) {
|
||||
|
||||
// Если статус "completed" - закрываем задачу если все исполнители выполнили
|
||||
if (status.status === 'completed') {
|
||||
await checkAndCloseTaskIfAllCompleted(status.task_id);
|
||||
await checkAndCloseTaskIfAllCompleted(db, status.task_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,7 +1450,7 @@ function setupUpravlenieEndpoints(app, db) {
|
||||
});
|
||||
|
||||
// Вспомогательная функция для проверки и закрытия задачи
|
||||
async function checkAndCloseTaskIfAllCompleted(taskId) {
|
||||
async function checkAndCloseTaskIfAllCompleted(db, taskId) {
|
||||
return new Promise((resolve) => {
|
||||
db.all(
|
||||
'SELECT status FROM task_assignments WHERE task_id = ?',
|
||||
|
||||
Reference in New Issue
Block a user