upravlenie-service
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,15 @@
|
|||||||
// upravlenie-service.js
|
// upravlenie-service.js
|
||||||
/**
|
|
||||||
* Модуль управления подключениями между сервисами
|
|
||||||
* Позволяет организовать цепочку сервисов (организатор -> исполнители)
|
|
||||||
* Каждый сервис имеет общий service_id (1-4062) для идентификации в цепочке
|
|
||||||
*/
|
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
// Константы
|
|
||||||
const SERVICE_ID_RANGE = { min: 1, max: 4062 };
|
const SERVICE_ID_RANGE = { min: 1, max: 4062 };
|
||||||
const SYNC_INTERVAL = 60000; // 1 минута
|
const SYNC_INTERVAL = 60000;
|
||||||
const MAX_RETRY_COUNT = 3;
|
const MAX_RETRY_COUNT = 3;
|
||||||
const RETRY_DELAY = 5000; // 5 секунд
|
const RETRY_DELAY = 5000;
|
||||||
|
|
||||||
// Статусы задач для синхронизации
|
|
||||||
const TaskStatus = {
|
const TaskStatus = {
|
||||||
PENDING: 'pending',
|
PENDING: 'pending',
|
||||||
IN_PROGRESS: 'in_progress',
|
IN_PROGRESS: 'in_progress',
|
||||||
@@ -28,24 +20,19 @@ const TaskStatus = {
|
|||||||
CANCELLED: 'cancelled'
|
CANCELLED: 'cancelled'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Направление синхронизации
|
|
||||||
const SyncDirection = {
|
const SyncDirection = {
|
||||||
INCOMING: 'incoming', // Получение задач от организатора
|
INCOMING: 'incoming',
|
||||||
OUTGOING: 'outgoing' // Отправка статусов исполнителю
|
OUTGOING: 'outgoing'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Класс для работы с подключениями между сервисами
|
|
||||||
class UpravlenieService {
|
class UpravlenieService {
|
||||||
constructor(db) {
|
constructor(db) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.syncIntervals = new Map(); // Интервалы синхронизации для каждого подключения
|
this.syncIntervals = new Map();
|
||||||
this.syncInProgress = new Set(); // Подключения в процессе синхронизации
|
this.syncInProgress = new Set();
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Инициализация сервиса
|
|
||||||
*/
|
|
||||||
async init() {
|
async init() {
|
||||||
console.log('🔧 Инициализация сервиса Upravlenie...');
|
console.log('🔧 Инициализация сервиса Upravlenie...');
|
||||||
await this.createTable();
|
await this.createTable();
|
||||||
@@ -53,9 +40,6 @@ class UpravlenieService {
|
|||||||
console.log('✅ Сервис Upravlenie инициализирован');
|
console.log('✅ Сервис Upravlenie инициализирован');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Создание таблицы upravlenie
|
|
||||||
*/
|
|
||||||
async createTable() {
|
async createTable() {
|
||||||
const createTableSQL = `
|
const createTableSQL = `
|
||||||
CREATE TABLE IF NOT EXISTS upravlenie (
|
CREATE TABLE IF NOT EXISTS upravlenie (
|
||||||
@@ -94,9 +78,6 @@ class UpravlenieService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Создание индексов для таблицы upravlenie
|
|
||||||
*/
|
|
||||||
async createIndexes() {
|
async createIndexes() {
|
||||||
const indexes = [
|
const indexes = [
|
||||||
'CREATE INDEX IF NOT EXISTS idx_upravlenie_service_id ON upravlenie(service_id)',
|
'CREATE INDEX IF NOT EXISTS idx_upravlenie_service_id ON upravlenie(service_id)',
|
||||||
@@ -126,27 +107,21 @@ class UpravlenieService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async createConnection(data) {
|
||||||
* Создание нового подключения
|
|
||||||
*/
|
|
||||||
async createConnection(data) {
|
|
||||||
const {
|
const {
|
||||||
service_id, service_name, service_type, login, password,
|
service_id, service_name, service_type, login, password,
|
||||||
api_url, local_user_id, local_user_login, sync_direction = 'outgoing',
|
api_url, local_user_id, local_user_login, sync_direction = 'outgoing',
|
||||||
sync_enabled = 1, sync_interval = 60
|
sync_enabled = 1, sync_interval = 60
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
// Валидация service_id
|
|
||||||
if (service_id < SERVICE_ID_RANGE.min || service_id > SERVICE_ID_RANGE.max) {
|
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}`);
|
throw new Error(`service_id должен быть в диапазоне ${SERVICE_ID_RANGE.min}-${SERVICE_ID_RANGE.max}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Для организатора api_url может быть пустым
|
|
||||||
if (service_type === 'executor' && !api_url) {
|
if (service_type === 'executor' && !api_url) {
|
||||||
throw new Error('Для исполнителя необходимо указать api_url организатора');
|
throw new Error('Для исполнителя необходимо указать api_url организатора');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем существование локального пользователя если указан
|
|
||||||
if (local_user_id) {
|
if (local_user_id) {
|
||||||
const userExists = await this.checkLocalUserExists(local_user_id);
|
const userExists = await this.checkLocalUserExists(local_user_id);
|
||||||
if (!userExists) {
|
if (!userExists) {
|
||||||
@@ -154,7 +129,6 @@ async createConnection(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем уникальность service_id для активных подключений
|
|
||||||
const existing = await this.getConnectionByServiceId(service_id);
|
const existing = await this.getConnectionByServiceId(service_id);
|
||||||
if (existing && existing.is_active) {
|
if (existing && existing.is_active) {
|
||||||
throw new Error(`Подключение с service_id ${service_id} уже существует`);
|
throw new Error(`Подключение с service_id ${service_id} уже существует`);
|
||||||
@@ -179,16 +153,20 @@ async createConnection(data) {
|
|||||||
} else {
|
} else {
|
||||||
const connectionId = this.lastID;
|
const connectionId = this.lastID;
|
||||||
console.log(`✅ Создано подключение ${service_name} (ID: ${connectionId}, service_id: ${service_id})`);
|
console.log(`✅ Создано подключение ${service_name} (ID: ${connectionId}, service_id: ${service_id})`);
|
||||||
|
|
||||||
resolve({ id: connectionId, ...data });
|
resolve({ id: connectionId, ...data });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkLocalUserExists(userId) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.db.get('SELECT id FROM users WHERE id = ?', [userId], (err, row) => {
|
||||||
|
resolve(!!row);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получение подключения по ID
|
|
||||||
*/
|
|
||||||
getConnection(id) {
|
getConnection(id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.db.get('SELECT * FROM upravlenie WHERE id = ?', [id], (err, row) => {
|
this.db.get('SELECT * FROM upravlenie WHERE id = ?', [id], (err, row) => {
|
||||||
@@ -198,9 +176,6 @@ async createConnection(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получение подключения по service_id
|
|
||||||
*/
|
|
||||||
getConnectionByServiceId(serviceId) {
|
getConnectionByServiceId(serviceId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.db.get(
|
this.db.get(
|
||||||
@@ -214,9 +189,6 @@ async createConnection(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получение всех подключений
|
|
||||||
*/
|
|
||||||
getAllConnections(filters = {}) {
|
getAllConnections(filters = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let sql = 'SELECT * FROM upravlenie WHERE 1=1';
|
let sql = 'SELECT * FROM upravlenie WHERE 1=1';
|
||||||
@@ -246,9 +218,6 @@ async createConnection(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновление подключения
|
|
||||||
*/
|
|
||||||
async updateConnection(id, data) {
|
async updateConnection(id, data) {
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
@@ -288,16 +257,10 @@ async createConnection(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Удаление подключения (мягкое удаление)
|
|
||||||
*/
|
|
||||||
async deleteConnection(id) {
|
async deleteConnection(id) {
|
||||||
return this.updateConnection(id, { is_active: 0 });
|
return this.updateConnection(id, { is_active: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Запуск всех задач синхронизации
|
|
||||||
*/
|
|
||||||
async startAllSyncJobs() {
|
async startAllSyncJobs() {
|
||||||
const connections = await this.getAllConnections({
|
const connections = await this.getAllConnections({
|
||||||
is_active: true,
|
is_active: true,
|
||||||
@@ -311,9 +274,6 @@ async createConnection(data) {
|
|||||||
console.log(`✅ Запущено ${connections.length} задач синхронизации`);
|
console.log(`✅ Запущено ${connections.length} задач синхронизации`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Запуск задачи синхронизации для конкретного подключения
|
|
||||||
*/
|
|
||||||
startSyncJob(connectionId, intervalMinutes) {
|
startSyncJob(connectionId, intervalMinutes) {
|
||||||
this.stopSyncJob(connectionId);
|
this.stopSyncJob(connectionId);
|
||||||
|
|
||||||
@@ -325,15 +285,11 @@ async createConnection(data) {
|
|||||||
this.syncIntervals.set(connectionId, interval);
|
this.syncIntervals.set(connectionId, interval);
|
||||||
console.log(`✅ Запущена синхронизация для подключения ${connectionId} (интервал: ${intervalMinutes} мин)`);
|
console.log(`✅ Запущена синхронизация для подключения ${connectionId} (интервал: ${intervalMinutes} мин)`);
|
||||||
|
|
||||||
// Выполняем первую синхронизацию сразу
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.syncConnection(connectionId);
|
this.syncConnection(connectionId);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Остановка задачи синхронизации
|
|
||||||
*/
|
|
||||||
stopSyncJob(connectionId) {
|
stopSyncJob(connectionId) {
|
||||||
if (this.syncIntervals.has(connectionId)) {
|
if (this.syncIntervals.has(connectionId)) {
|
||||||
clearInterval(this.syncIntervals.get(connectionId));
|
clearInterval(this.syncIntervals.get(connectionId));
|
||||||
@@ -342,9 +298,6 @@ async createConnection(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Перезапуск задачи синхронизации
|
|
||||||
*/
|
|
||||||
async restartSyncJob(connectionId) {
|
async restartSyncJob(connectionId) {
|
||||||
this.stopSyncJob(connectionId);
|
this.stopSyncJob(connectionId);
|
||||||
|
|
||||||
@@ -354,11 +307,7 @@ async createConnection(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Синхронизация с подключенным сервисом
|
|
||||||
*/
|
|
||||||
async syncConnection(connectionId) {
|
async syncConnection(connectionId) {
|
||||||
// Предотвращаем одновременную синхронизацию одного подключения
|
|
||||||
if (this.syncInProgress.has(connectionId)) {
|
if (this.syncInProgress.has(connectionId)) {
|
||||||
console.log(`⚠️ Синхронизация подключения ${connectionId} уже выполняется`);
|
console.log(`⚠️ Синхронизация подключения ${connectionId} уже выполняется`);
|
||||||
return;
|
return;
|
||||||
@@ -374,14 +323,11 @@ async createConnection(data) {
|
|||||||
|
|
||||||
console.log(`🔄 Синхронизация подключения ${connection.service_name} (${connection.service_id})...`);
|
console.log(`🔄 Синхронизация подключения ${connection.service_name} (${connection.service_id})...`);
|
||||||
|
|
||||||
// Обновляем время последней синхронизации
|
|
||||||
await this.updateSyncStatus(connectionId, 'in_progress');
|
await this.updateSyncStatus(connectionId, 'in_progress');
|
||||||
|
|
||||||
if (connection.service_type === 'organizer') {
|
if (connection.service_type === 'organizer') {
|
||||||
// Для организатора: получаем статусы от исполнителей
|
|
||||||
await this.syncFromExecutors(connection);
|
await this.syncFromExecutors(connection);
|
||||||
} else {
|
} else {
|
||||||
// Для исполнителя: отправляем статусы организатору
|
|
||||||
await this.syncToOrganizer(connection);
|
await this.syncToOrganizer(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,9 +342,6 @@ async createConnection(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновление статуса синхронизации
|
|
||||||
*/
|
|
||||||
updateSyncStatus(connectionId, status, error = null) {
|
updateSyncStatus(connectionId, status, error = null) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.db.run(
|
this.db.run(
|
||||||
@@ -416,43 +359,52 @@ async createConnection(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async syncToOrganizer(connection) {
|
||||||
* Синхронизация от исполнителя к организатору
|
|
||||||
*/
|
|
||||||
async syncToOrganizer(connection) {
|
|
||||||
if (!connection.api_url) {
|
if (!connection.api_url) {
|
||||||
throw new Error('Для исполнителя не указан api_url организатора');
|
throw new Error('Для исполнителя не указан api_url организатора');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем наличие локального пользователя
|
console.log(`🔍 Исполнитель ${connection.service_name} пытается связаться с организатором по URL: ${connection.api_url}`);
|
||||||
|
|
||||||
if (!connection.local_user_id) {
|
if (!connection.local_user_id) {
|
||||||
console.warn(`⚠️ Для подключения ${connection.service_name} не указан локальный пользователь. Задачи не будут создаваться.`);
|
console.warn(`⚠️ Для подключения ${connection.service_name} не указан локальный пользователь. Задачи не будут создаваться.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем задачи, назначенные локальному пользователю
|
try {
|
||||||
|
console.log(`📡 Проверка доступности организатора...`);
|
||||||
|
|
||||||
|
const organizerTasks = await this.fetchTasksFromOrganizer(connection);
|
||||||
|
console.log(`📥 Получено ${organizerTasks.length} задач от организатора`);
|
||||||
|
|
||||||
const localTasks = await this.getTasksForLocalUser(connection.local_user_id);
|
const localTasks = await this.getTasksForLocalUser(connection.local_user_id);
|
||||||
|
|
||||||
// Получаем задачи от организатора
|
if (organizerTasks.length > 0) {
|
||||||
const organizerTasks = await this.fetchTasksFromOrganizer(connection);
|
await this.syncTasksWithOrganizer(connection, organizerTasks, localTasks);
|
||||||
|
|
||||||
if (organizerTasks.length === 0) {
|
|
||||||
console.log(`📭 Нет новых задач от организатора для ${connection.service_name}`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем локальные задачи
|
if (localTasks.length > 0) {
|
||||||
await this.syncTasksWithOrganizer(connection, organizerTasks, localTasks);
|
|
||||||
|
|
||||||
// Отправляем обновленные статусы организатору
|
|
||||||
await this.sendTaskStatusesToOrganizer(connection, localTasks);
|
await this.sendTaskStatusesToOrganizer(connection, localTasks);
|
||||||
}
|
console.log(`📤 Отправлено ${localTasks.length} статусов организатору`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
} catch (error) {
|
||||||
* Синхронизация от организатора к исполнителям
|
console.error(`❌ Ошибка связи с организатором:`, error.message);
|
||||||
*/
|
if (error.code === 'ECONNREFUSED') {
|
||||||
async syncFromExecutors(connection) {
|
console.error(`🔴 Организатор не доступен по адресу ${connection.api_url}`);
|
||||||
// Получаем все активные подключения исполнителей с таким же service_id
|
} else if (error.code === 'ETIMEDOUT') {
|
||||||
|
console.error(`⏱️ Таймаут при подключении к организатору`);
|
||||||
|
} else if (error.response) {
|
||||||
|
console.error(`📡 Ответ организатора:`, {
|
||||||
|
status: error.response.status,
|
||||||
|
data: error.response.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncFromExecutors(connection) {
|
||||||
const executors = await this.getAllConnections({
|
const executors = await this.getAllConnections({
|
||||||
service_type: 'executor',
|
service_type: 'executor',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
@@ -466,17 +418,14 @@ async syncFromExecutors(connection) {
|
|||||||
|
|
||||||
for (const executor of executors) {
|
for (const executor of executors) {
|
||||||
try {
|
try {
|
||||||
// Проверяем наличие локального пользователя у исполнителя
|
|
||||||
if (!executor.local_user_id) {
|
if (!executor.local_user_id) {
|
||||||
console.warn(`⚠️ Для исполнителя ${executor.service_name} не указан локальный пользователь. Пропускаем...`);
|
console.warn(`⚠️ Для исполнителя ${executor.service_name} не указан локальный пользователь. Пропускаем...`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем статусы задач от исполнителя
|
|
||||||
const taskStatuses = await this.fetchTaskStatusesFromExecutor(executor);
|
const taskStatuses = await this.fetchTaskStatusesFromExecutor(executor);
|
||||||
|
|
||||||
if (taskStatuses.length > 0) {
|
if (taskStatuses.length > 0) {
|
||||||
// Обновляем статусы в локальной БД
|
|
||||||
await this.updateTaskStatusesFromExecutor(executor, taskStatuses);
|
await this.updateTaskStatusesFromExecutor(executor, taskStatuses);
|
||||||
console.log(`✅ Получено ${taskStatuses.length} статусов от исполнителя ${executor.service_name}`);
|
console.log(`✅ Получено ${taskStatuses.length} статусов от исполнителя ${executor.service_name}`);
|
||||||
}
|
}
|
||||||
@@ -485,20 +434,8 @@ async syncFromExecutors(connection) {
|
|||||||
console.error(`❌ Ошибка получения статусов от исполнителя ${executor.service_name}:`, error.message);
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Получение задач для локального пользователя
|
|
||||||
*/
|
|
||||||
getTasksForLocalUser(userId) {
|
getTasksForLocalUser(userId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
@@ -518,9 +455,6 @@ async checkLocalUserExists(userId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получение задач от организатора через API
|
|
||||||
*/
|
|
||||||
async fetchTasksFromOrganizer(connection) {
|
async fetchTasksFromOrganizer(connection) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${connection.api_url}/api/external/tasks`, {
|
const response = await axios.get(`${connection.api_url}/api/external/tasks`, {
|
||||||
@@ -542,9 +476,6 @@ async checkLocalUserExists(userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получение статусов задач от исполнителя
|
|
||||||
*/
|
|
||||||
async fetchTaskStatusesFromExecutor(executor) {
|
async fetchTaskStatusesFromExecutor(executor) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${executor.api_url}/api/external/task-statuses`, {
|
const response = await axios.get(`${executor.api_url}/api/external/task-statuses`, {
|
||||||
@@ -565,9 +496,6 @@ async checkLocalUserExists(userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправка статусов задач организатору
|
|
||||||
*/
|
|
||||||
async sendTaskStatusesToOrganizer(connection, localTasks) {
|
async sendTaskStatusesToOrganizer(connection, localTasks) {
|
||||||
const statuses = localTasks.map(task => ({
|
const statuses = localTasks.map(task => ({
|
||||||
task_id: task.id,
|
task_id: task.id,
|
||||||
@@ -596,37 +524,25 @@ async checkLocalUserExists(userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Синхронизация задач с организатором
|
|
||||||
*/
|
|
||||||
async syncTasksWithOrganizer(connection, organizerTasks, localTasks) {
|
async syncTasksWithOrganizer(connection, organizerTasks, localTasks) {
|
||||||
for (const organizerTask of organizerTasks) {
|
for (const organizerTask of organizerTasks) {
|
||||||
const localTask = localTasks.find(t => t.external_task_id === organizerTask.id);
|
const localTask = localTasks.find(t => t.external_task_id === organizerTask.id);
|
||||||
|
|
||||||
if (!localTask) {
|
if (!localTask) {
|
||||||
// Новая задача от организатора - создаем локально
|
|
||||||
await this.createTaskFromOrganizer(connection, organizerTask);
|
await this.createTaskFromOrganizer(connection, organizerTask);
|
||||||
} else {
|
} else {
|
||||||
// Существующая задача - обновляем если нужно
|
|
||||||
await this.updateTaskFromOrganizer(connection, organizerTask, localTask);
|
await this.updateTaskFromOrganizer(connection, organizerTask, localTask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// upravlenie-service.js (исправленная версия метода createTaskFromOrganizer)
|
async createTaskFromOrganizer(connection, taskData) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Создание задачи из данных организатора
|
|
||||||
*/
|
|
||||||
async createTaskFromOrganizer(connection, taskData) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Проверяем, что local_user_id не NULL
|
|
||||||
if (!connection.local_user_id) {
|
if (!connection.local_user_id) {
|
||||||
reject(new Error('local_user_id не может быть пустым. Укажите локального пользователя для создания задач.'));
|
reject(new Error('local_user_id не может быть пустым. Укажите локального пользователя для создания задач.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сначала проверяем структуру таблицы tasks
|
|
||||||
this.db.all("PRAGMA table_info(tasks)", (err, columns) => {
|
this.db.all("PRAGMA table_info(tasks)", (err, columns) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -635,27 +551,24 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
|
|
||||||
const columnNames = columns.map(c => c.name);
|
const columnNames = columns.map(c => c.name);
|
||||||
|
|
||||||
// Формируем SQL запрос динамически
|
|
||||||
let fields = ['title', 'description', 'status', 'created_by', 'start_date', 'due_date', 'task_type'];
|
let fields = ['title', 'description', 'status', 'created_by', 'start_date', 'due_date', 'task_type'];
|
||||||
let placeholders = ['?', '?', '?', '?', '?', '?', '?'];
|
let placeholders = ['?', '?', '?', '?', '?', '?', '?'];
|
||||||
let values = [
|
let values = [
|
||||||
taskData.title,
|
taskData.title,
|
||||||
taskData.description || '',
|
taskData.description || '',
|
||||||
'active',
|
'active',
|
||||||
connection.local_user_id, // теперь точно не NULL
|
connection.local_user_id,
|
||||||
taskData.start_date || new Date().toISOString(),
|
taskData.start_date || new Date().toISOString(),
|
||||||
taskData.due_date || null,
|
taskData.due_date || null,
|
||||||
'external'
|
'external'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Добавляем external_task_id если колонка существует
|
|
||||||
if (columnNames.includes('external_task_id')) {
|
if (columnNames.includes('external_task_id')) {
|
||||||
fields.push('external_task_id');
|
fields.push('external_task_id');
|
||||||
placeholders.push('?');
|
placeholders.push('?');
|
||||||
values.push(taskData.id);
|
values.push(taskData.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем external_service_id если колонка существует
|
|
||||||
if (columnNames.includes('external_service_id')) {
|
if (columnNames.includes('external_service_id')) {
|
||||||
fields.push('external_service_id');
|
fields.push('external_service_id');
|
||||||
placeholders.push('?');
|
placeholders.push('?');
|
||||||
@@ -664,23 +577,14 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
|
|
||||||
const sql = `INSERT INTO tasks (${fields.join(', ')}) VALUES (${placeholders.join(', ')})`;
|
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) {
|
this.db.run(sql, values, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('❌ Ошибка создания задачи:', err);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTaskId = this.lastID;
|
const newTaskId = this.lastID;
|
||||||
|
|
||||||
// Назначаем задачу локальному пользователю
|
|
||||||
this.db.run(
|
this.db.run(
|
||||||
`INSERT INTO task_assignments (task_id, user_id, status, start_date, due_date)
|
`INSERT INTO task_assignments (task_id, user_id, status, start_date, due_date)
|
||||||
VALUES (?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
@@ -693,7 +597,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
],
|
],
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('❌ Ошибка назначения задачи:', err);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -705,10 +608,8 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Обновление задачи из данных организатора
|
|
||||||
*/
|
|
||||||
async updateTaskFromOrganizer(connection, organizerTask, localTask) {
|
async updateTaskFromOrganizer(connection, organizerTask, localTask) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.db.run(
|
this.db.run(
|
||||||
@@ -727,9 +628,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновление статусов задач от исполнителя
|
|
||||||
*/
|
|
||||||
async updateTaskStatusesFromExecutor(executor, taskStatuses) {
|
async updateTaskStatusesFromExecutor(executor, taskStatuses) {
|
||||||
for (const status of taskStatuses) {
|
for (const status of taskStatuses) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
@@ -754,16 +652,12 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Если статус "completed" - проверяем и закрываем задачу если все выполнили
|
|
||||||
if (status.status === 'completed') {
|
if (status.status === 'completed') {
|
||||||
await this.checkAndCloseTaskIfAllCompleted(status.task_id);
|
await this.checkAndCloseTaskIfAllCompleted(status.task_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверка и закрытие задачи если все исполнители выполнили
|
|
||||||
*/
|
|
||||||
async checkAndCloseTaskIfAllCompleted(taskId) {
|
async checkAndCloseTaskIfAllCompleted(taskId) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.db.all(
|
this.db.all(
|
||||||
@@ -795,9 +689,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Загрузка файла в удаленный сервис
|
|
||||||
*/
|
|
||||||
async uploadFileToRemote(connection, taskId, fileId) {
|
async uploadFileToRemote(connection, taskId, fileId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.db.get(
|
this.db.get(
|
||||||
@@ -850,9 +741,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получение файла из удаленного сервиса
|
|
||||||
*/
|
|
||||||
async downloadFileFromRemote(connection, remoteFileId, localTaskId) {
|
async downloadFileFromRemote(connection, remoteFileId, localTaskId) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
@@ -868,7 +756,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Сохраняем файл локально
|
|
||||||
const { createUserTaskFolder } = require('./database');
|
const { createUserTaskFolder } = require('./database');
|
||||||
const userFolder = createUserTaskFolder(localTaskId, connection.local_user_login || 'external');
|
const userFolder = createUserTaskFolder(localTaskId, connection.local_user_login || 'external');
|
||||||
|
|
||||||
@@ -883,7 +770,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
writer.on('error', reject);
|
writer.on('error', reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Сохраняем запись в БД
|
|
||||||
const fileSize = fs.statSync(filePath).size;
|
const fileSize = fs.statSync(filePath).size;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -914,9 +800,6 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Генерация токена для внешней аутентификации
|
|
||||||
*/
|
|
||||||
generateAuthToken(serviceId, login) {
|
generateAuthToken(serviceId, login) {
|
||||||
const secret = process.env.EXTERNAL_API_SECRET || 'default_secret_change_me';
|
const secret = process.env.EXTERNAL_API_SECRET || 'default_secret_change_me';
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
@@ -925,15 +808,11 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
return `${timestamp}:${hash}`;
|
return `${timestamp}:${hash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверка токена внешней аутентификации
|
|
||||||
*/
|
|
||||||
verifyAuthToken(token, serviceId, login) {
|
verifyAuthToken(token, serviceId, login) {
|
||||||
try {
|
try {
|
||||||
const secret = process.env.EXTERNAL_API_SECRET || 'default_secret_change_me';
|
const secret = process.env.EXTERNAL_API_SECRET || 'default_secret_change_me';
|
||||||
const [timestamp, hash] = token.split(':');
|
const [timestamp, hash] = token.split(':');
|
||||||
|
|
||||||
// Проверяем, что токен не старше 5 минут
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
if (now - parseInt(timestamp) > 300) {
|
if (now - parseInt(timestamp) > 300) {
|
||||||
return false;
|
return false;
|
||||||
@@ -951,17 +830,73 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ручная синхронизация подключения
|
|
||||||
*/
|
|
||||||
async manualSync(connectionId) {
|
async manualSync(connectionId) {
|
||||||
console.log(`🔄 Ручная синхронизация подключения ${connectionId}`);
|
console.log(`🔄 Ручная синхронизация подключения ${connectionId}`);
|
||||||
await this.syncConnection(connectionId);
|
await this.syncConnection(connectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async checkConnection(connectionId) {
|
||||||
* Получение статистики синхронизации
|
const connection = await this.getConnection(connectionId);
|
||||||
*/
|
if (!connection) {
|
||||||
|
return { success: false, error: 'Подключение не найдено' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
id: connection.id,
|
||||||
|
service_id: connection.service_id,
|
||||||
|
service_name: connection.service_name,
|
||||||
|
service_type: connection.service_type,
|
||||||
|
local_user_id: connection.local_user_id,
|
||||||
|
api_url: connection.api_url,
|
||||||
|
status: 'unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (connection.service_type === 'executor') {
|
||||||
|
if (!connection.api_url) {
|
||||||
|
result.status = 'error';
|
||||||
|
result.error = 'Не указан API URL организатора';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${connection.api_url}/api/health`, {
|
||||||
|
timeout: 5000,
|
||||||
|
auth: {
|
||||||
|
username: connection.login,
|
||||||
|
password: connection.password
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result.status = 'connected';
|
||||||
|
result.organizer_status = response.data;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
result.status = 'disconnected';
|
||||||
|
result.error = error.message;
|
||||||
|
|
||||||
|
if (error.code === 'ECONNREFUSED') {
|
||||||
|
result.error_details = 'Организатор не доступен по указанному адресу';
|
||||||
|
} else if (error.response?.status === 401) {
|
||||||
|
result.error_details = 'Ошибка аутентификации - проверьте логин/пароль';
|
||||||
|
} else if (error.response?.status === 404) {
|
||||||
|
result.error_details = 'API организатора не найдено - проверьте URL';
|
||||||
|
} else {
|
||||||
|
result.error_details = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const executors = await this.getAllConnections({
|
||||||
|
service_type: 'executor',
|
||||||
|
is_active: true
|
||||||
|
});
|
||||||
|
|
||||||
|
result.executors_count = executors.length;
|
||||||
|
result.status = executors.length > 0 ? 'has_executors' : 'no_executors';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
async getSyncStats() {
|
async getSyncStats() {
|
||||||
const connections = await this.getAllConnections({ is_active: true });
|
const connections = await this.getAllConnections({ is_active: true });
|
||||||
|
|
||||||
@@ -1001,11 +936,9 @@ async createTaskFromOrganizer(connection, taskData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для настройки API endpoints
|
|
||||||
function setupUpravlenieEndpoints(app, db) {
|
function setupUpravlenieEndpoints(app, db) {
|
||||||
const upravlenieService = new UpravlenieService(db);
|
const upravlenieService = new UpravlenieService(db);
|
||||||
|
|
||||||
// Middleware для проверки аутентификации
|
|
||||||
const requireAuth = (req, res, next) => {
|
const requireAuth = (req, res, next) => {
|
||||||
if (!req.session || !req.session.user) {
|
if (!req.session || !req.session.user) {
|
||||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||||
@@ -1013,7 +946,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Middleware для проверки прав администратора
|
|
||||||
const requireAdmin = (req, res, next) => {
|
const requireAdmin = (req, res, next) => {
|
||||||
if (!req.session || !req.session.user) {
|
if (!req.session || !req.session.user) {
|
||||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||||
@@ -1024,12 +956,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== ВНУТРЕННИЕ API (для администрирования) ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/upravlenie/connections
|
|
||||||
* Получение всех подключений
|
|
||||||
*/
|
|
||||||
app.get('/api/upravlenie/connections', requireAdmin, async (req, res) => {
|
app.get('/api/upravlenie/connections', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const filters = {
|
const filters = {
|
||||||
@@ -1046,10 +972,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/upravlenie/connections/:id
|
|
||||||
* Получение подключения по ID
|
|
||||||
*/
|
|
||||||
app.get('/api/upravlenie/connections/:id', requireAdmin, async (req, res) => {
|
app.get('/api/upravlenie/connections/:id', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const connection = await upravlenieService.getConnection(req.params.id);
|
const connection = await upravlenieService.getConnection(req.params.id);
|
||||||
@@ -1062,10 +984,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/upravlenie/connections
|
|
||||||
* Создание нового подключения
|
|
||||||
*/
|
|
||||||
app.post('/api/upravlenie/connections', requireAdmin, async (req, res) => {
|
app.post('/api/upravlenie/connections', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const connection = await upravlenieService.createConnection(req.body);
|
const connection = await upravlenieService.createConnection(req.body);
|
||||||
@@ -1080,10 +998,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT /api/upravlenie/connections/:id
|
|
||||||
* Обновление подключения
|
|
||||||
*/
|
|
||||||
app.put('/api/upravlenie/connections/:id', requireAdmin, async (req, res) => {
|
app.put('/api/upravlenie/connections/:id', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const result = await upravlenieService.updateConnection(req.params.id, req.body);
|
const result = await upravlenieService.updateConnection(req.params.id, req.body);
|
||||||
@@ -1097,10 +1011,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE /api/upravlenie/connections/:id
|
|
||||||
* Удаление подключения
|
|
||||||
*/
|
|
||||||
app.delete('/api/upravlenie/connections/:id', requireAdmin, async (req, res) => {
|
app.delete('/api/upravlenie/connections/:id', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await upravlenieService.deleteConnection(req.params.id);
|
await upravlenieService.deleteConnection(req.params.id);
|
||||||
@@ -1110,10 +1020,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/upravlenie/connections/:id/sync
|
|
||||||
* Ручная синхронизация подключения
|
|
||||||
*/
|
|
||||||
app.post('/api/upravlenie/connections/:id/sync', requireAdmin, async (req, res) => {
|
app.post('/api/upravlenie/connections/:id/sync', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await upravlenieService.manualSync(req.params.id);
|
await upravlenieService.manualSync(req.params.id);
|
||||||
@@ -1123,10 +1029,15 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
app.get('/api/upravlenie/check/:id', requireAdmin, async (req, res) => {
|
||||||
* GET /api/upravlenie/stats
|
try {
|
||||||
* Статистика синхронизации
|
const result = await upravlenieService.checkConnection(req.params.id);
|
||||||
*/
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/upravlenie/stats', requireAdmin, async (req, res) => {
|
app.get('/api/upravlenie/stats', requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const stats = await upravlenieService.getSyncStats();
|
const stats = await upravlenieService.getSyncStats();
|
||||||
@@ -1136,11 +1047,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ==================== ВНЕШНИЕ API (для взаимодействия между сервисами) ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware для аутентификации внешних запросов
|
|
||||||
*/
|
|
||||||
const authenticateExternal = async (req, res, next) => {
|
const authenticateExternal = async (req, res, next) => {
|
||||||
const authHeader = req.headers.authorization;
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
@@ -1153,7 +1059,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
const [login, password] = credentials.split(':');
|
const [login, password] = credentials.split(':');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ищем подключение по логину и паролю
|
|
||||||
const connection = await new Promise((resolve, reject) => {
|
const connection = await new Promise((resolve, reject) => {
|
||||||
db.get(
|
db.get(
|
||||||
'SELECT * FROM upravlenie WHERE login = ? AND password = ? AND is_active = 1',
|
'SELECT * FROM upravlenie WHERE login = ? AND password = ? AND is_active = 1',
|
||||||
@@ -1177,22 +1082,15 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/external/tasks
|
|
||||||
* Получение задач для внешнего сервиса
|
|
||||||
* (используется исполнителем для получения задач от организатора)
|
|
||||||
*/
|
|
||||||
app.get('/api/external/tasks', authenticateExternal, async (req, res) => {
|
app.get('/api/external/tasks', authenticateExternal, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { service_id, user_login } = req.query;
|
const { service_id, user_login } = req.query;
|
||||||
const connection = req.externalConnection;
|
const connection = req.externalConnection;
|
||||||
|
|
||||||
// Проверяем соответствие service_id
|
|
||||||
if (parseInt(service_id) !== connection.service_id) {
|
if (parseInt(service_id) !== connection.service_id) {
|
||||||
return res.status(403).json({ error: 'Неверный service_id' });
|
return res.status(403).json({ error: 'Неверный service_id' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если указан user_login, ищем задачи для этого пользователя
|
|
||||||
let userId = null;
|
let userId = null;
|
||||||
if (user_login) {
|
if (user_login) {
|
||||||
const user = await new Promise((resolve) => {
|
const user = await new Promise((resolve) => {
|
||||||
@@ -1203,10 +1101,8 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
userId = user?.id;
|
userId = user?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем задачи
|
|
||||||
let tasks = [];
|
let tasks = [];
|
||||||
if (userId) {
|
if (userId) {
|
||||||
// Задачи для конкретного пользователя
|
|
||||||
tasks = await new Promise((resolve) => {
|
tasks = await new Promise((resolve) => {
|
||||||
db.all(`
|
db.all(`
|
||||||
SELECT t.*, ta.status as assignment_status
|
SELECT t.*, ta.status as assignment_status
|
||||||
@@ -1218,7 +1114,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Все активные задачи
|
|
||||||
tasks = await new Promise((resolve) => {
|
tasks = await new Promise((resolve) => {
|
||||||
db.all(`
|
db.all(`
|
||||||
SELECT t.*, GROUP_CONCAT(ta.user_id) as assigned_users
|
SELECT t.*, GROUP_CONCAT(ta.user_id) as assigned_users
|
||||||
@@ -1251,10 +1146,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/external/task-statuses
|
|
||||||
* Получение статусов задач от исполнителя
|
|
||||||
*/
|
|
||||||
app.post('/api/external/task-statuses', authenticateExternal, async (req, res) => {
|
app.post('/api/external/task-statuses', authenticateExternal, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { service_id, statuses } = req.body;
|
const { service_id, statuses } = req.body;
|
||||||
@@ -1268,7 +1159,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
return res.status(400).json({ error: 'statuses должен быть массивом' });
|
return res.status(400).json({ error: 'statuses должен быть массивом' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем статусы в локальной БД
|
|
||||||
for (const status of statuses) {
|
for (const status of statuses) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
db.run(
|
db.run(
|
||||||
@@ -1283,7 +1173,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Если статус "completed" - закрываем задачу если все исполнители выполнили
|
|
||||||
if (status.status === 'completed') {
|
if (status.status === 'completed') {
|
||||||
await checkAndCloseTaskIfAllCompleted(db, status.task_id);
|
await checkAndCloseTaskIfAllCompleted(db, status.task_id);
|
||||||
}
|
}
|
||||||
@@ -1301,10 +1190,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/external/task-statuses
|
|
||||||
* Получение статусов задач для организатора
|
|
||||||
*/
|
|
||||||
app.get('/api/external/task-statuses', authenticateExternal, async (req, res) => {
|
app.get('/api/external/task-statuses', authenticateExternal, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { service_id } = req.query;
|
const { service_id } = req.query;
|
||||||
@@ -1314,7 +1199,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
return res.status(403).json({ error: 'Неверный service_id' });
|
return res.status(403).json({ error: 'Неверный service_id' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем задачи, созданные для этого внешнего сервиса
|
|
||||||
const statuses = await new Promise((resolve) => {
|
const statuses = await new Promise((resolve) => {
|
||||||
db.all(`
|
db.all(`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -1342,10 +1226,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/external/upload-file
|
|
||||||
* Загрузка файла от исполнителя
|
|
||||||
*/
|
|
||||||
app.post('/api/external/upload-file', authenticateExternal, async (req, res) => {
|
app.post('/api/external/upload-file', authenticateExternal, async (req, res) => {
|
||||||
if (!req.files || !req.files.file) {
|
if (!req.files || !req.files.file) {
|
||||||
return res.status(400).json({ error: 'Файл не загружен' });
|
return res.status(400).json({ error: 'Файл не загружен' });
|
||||||
@@ -1360,17 +1240,14 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
return res.status(403).json({ error: 'Неверный service_id' });
|
return res.status(403).json({ error: 'Неверный service_id' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем папку для задачи
|
|
||||||
const { createUserTaskFolder } = require('./database');
|
const { createUserTaskFolder } = require('./database');
|
||||||
const userFolder = createUserTaskFolder(task_id, connection.local_user_login || 'external');
|
const userFolder = createUserTaskFolder(task_id, connection.local_user_login || 'external');
|
||||||
|
|
||||||
// Сохраняем файл
|
|
||||||
const fileName = `${Date.now()}_${file.name}`;
|
const fileName = `${Date.now()}_${file.name}`;
|
||||||
const filePath = path.join(userFolder, fileName);
|
const filePath = path.join(userFolder, fileName);
|
||||||
|
|
||||||
await file.mv(filePath);
|
await file.mv(filePath);
|
||||||
|
|
||||||
// Сохраняем запись в БД
|
|
||||||
const result = await new Promise((resolve, reject) => {
|
const result = await new Promise((resolve, reject) => {
|
||||||
db.run(
|
db.run(
|
||||||
`INSERT INTO task_files (task_id, user_id, filename, original_name, file_path, file_size)
|
`INSERT INTO task_files (task_id, user_id, filename, original_name, file_path, file_size)
|
||||||
@@ -1402,10 +1279,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/external/download-file/:fileId
|
|
||||||
* Скачивание файла для исполнителя
|
|
||||||
*/
|
|
||||||
app.get('/api/external/download-file/:fileId', authenticateExternal, async (req, res) => {
|
app.get('/api/external/download-file/:fileId', authenticateExternal, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { fileId } = req.params;
|
const { fileId } = req.params;
|
||||||
@@ -1416,7 +1289,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
return res.status(403).json({ error: 'Неверный service_id' });
|
return res.status(403).json({ error: 'Неверный service_id' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем информацию о файле
|
|
||||||
const file = await new Promise((resolve, reject) => {
|
const file = await new Promise((resolve, reject) => {
|
||||||
db.get(
|
db.get(
|
||||||
'SELECT * FROM task_files WHERE id = ?',
|
'SELECT * FROM task_files WHERE id = ?',
|
||||||
@@ -1436,7 +1308,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
return res.status(404).json({ error: 'Файл не существует на диске' });
|
return res.status(404).json({ error: 'Файл не существует на диске' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем файл
|
|
||||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(file.original_name)}"`);
|
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(file.original_name)}"`);
|
||||||
res.setHeader('X-File-Name', encodeURIComponent(file.original_name));
|
res.setHeader('X-File-Name', encodeURIComponent(file.original_name));
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
@@ -1449,7 +1320,6 @@ function setupUpravlenieEndpoints(app, db) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Вспомогательная функция для проверки и закрытия задачи
|
|
||||||
async function checkAndCloseTaskIfAllCompleted(db, taskId) {
|
async function checkAndCloseTaskIfAllCompleted(db, taskId) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
db.all(
|
db.all(
|
||||||
|
|||||||
Reference in New Issue
Block a user