upravlenie-service

This commit is contained in:
2026-02-25 16:00:39 +05:00
parent f4795d019e
commit 27fd3543ec

View File

@@ -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 = ?',