api-client

This commit is contained in:
2026-03-09 14:20:28 +05:00
parent 08df44734c
commit 2a492aaa7c
4 changed files with 253 additions and 513 deletions

View File

@@ -444,245 +444,6 @@ module.exports = function(app, db, upload) {
}
});
/**
* POST /api/client/tasks/:taskId/sync - Синхронизировать ЛОКАЛЬНУЮ задачу с целевым сервисом
*/
router.post('/api/client/tasks/:taskId/sync', requireAuth, async (req, res) => {
const { taskId } = req.params;
const {
target_connection_id,
target_api_url,
target_api_key,
sync_files = true
} = req.body;
const userId = req.session.user.id;
// Определяем целевой сервис
let targetUrl = target_api_url;
let targetKey = target_api_key;
if (target_connection_id && req.session.clientConnections && req.session.clientConnections[target_connection_id]) {
const connection = req.session.clientConnections[target_connection_id];
targetUrl = connection.url;
targetKey = connection.api_key;
}
if (!targetUrl || !targetKey) {
return res.status(400).json({
error: 'Не указан целевой сервис (URL или API ключ)'
});
}
const baseUrl = targetUrl.replace(/\/$/, '');
try {
// 1. Получаем задачу из ЛОКАЛЬНОЙ базы данных
const localTask = await new Promise((resolve, reject) => {
db.get(`
SELECT t.*, u.name as creator_name, u.login as creator_login
FROM tasks t
LEFT JOIN users u ON t.created_by = u.id
WHERE t.id = ?
`, [taskId], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
if (!localTask) {
return res.status(404).json({ error: 'Локальная задача не найдена' });
}
// Преобразуем в формат, аналогичный внешнему API
const sourceTask = {
id: localTask.id,
title: localTask.title,
description: localTask.description || '',
due_date: localTask.due_date,
task_type: localTask.task_type || 'regular',
files: await getLocalTaskFiles(taskId)
};
// 2. Проверяем, существует ли задача в целевой системе (поиск по ID)
let existingTask = null;
try {
const checkResponse = await axios.get(
`${baseUrl}/api/external/tasks/${taskId}`,
{
headers: { 'X-API-Key': targetKey },
timeout: 5000
}
);
if (checkResponse.data && checkResponse.data.success) {
existingTask = checkResponse.data.task;
}
} catch (checkError) {
// Задача не найдена - это нормально, будем создавать новую
console.log('Задача не найдена в целевой системе, будет создана новая');
}
let result;
const syncedFiles = [];
const warnings = [];
if (existingTask) {
// 3. Обновляем существующую задачу
const updateData = {
title: sourceTask.title,
description: sourceTask.description,
due_date: sourceTask.due_date,
task_type: sourceTask.task_type
};
const updateResponse = await axios.put(
`${baseUrl}/api/external/tasks/${taskId}`,
updateData,
{
headers: {
'X-API-Key': targetKey,
'Content-Type': 'application/json'
},
timeout: 15000
}
);
if (!updateResponse.data || !updateResponse.data.success) {
return res.status(500).json({ error: 'Не удалось обновить задачу в целевом сервисе' });
}
result = { taskId: taskId, action: 'updated' };
} else {
// 4. Создаём новую задачу
const taskData = {
title: sourceTask.title,
description: sourceTask.description,
due_date: sourceTask.due_date,
task_type: sourceTask.task_type
};
const createResponse = await axios.post(
`${baseUrl}/api/external/tasks/create`,
taskData,
{
headers: {
'X-API-Key': targetKey,
'Content-Type': 'application/json'
},
timeout: 15000
}
);
if (!createResponse.data || !createResponse.data.success) {
return res.status(500).json({ error: 'Не удалось создать задачу в целевом сервисе' });
}
result = {
taskId: createResponse.data.taskId,
action: 'created'
};
}
// 5. Синхронизируем файлы, если нужно
if (sync_files && sourceTask.files && sourceTask.files.length > 0) {
for (const file of sourceTask.files) {
try {
// Читаем файл с диска
if (!fs.existsSync(file.file_path)) {
throw new Error(`Файл не найден на диске: ${file.file_path}`);
}
const fileBuffer = fs.readFileSync(file.file_path);
const fileName = file.original_name || path.basename(file.file_path);
// Загружаем в целевую систему
const formData = new FormData();
formData.append('files', fileBuffer, {
filename: fileName,
contentType: file.file_type || 'application/octet-stream'
});
const uploadResponse = await axios.post(
`${baseUrl}/api/external/tasks/${result.taskId}/files`,
formData,
{
headers: {
...formData.getHeaders(),
'X-API-Key': targetKey
},
timeout: 60000,
maxContentLength: Infinity,
maxBodyLength: Infinity
}
);
syncedFiles.push({
original_name: fileName,
success: uploadResponse.data && uploadResponse.data.success
});
} catch (fileError) {
console.error(`❌ Ошибка синхронизации файла:`, fileError.message);
syncedFiles.push({
original_name: file.original_name || 'unknown',
success: false,
error: fileError.message
});
warnings.push(`Не удалось синхронизировать файл: ${file.original_name || file.filename}`);
}
}
}
// Логируем действие
const { logActivity } = require('./database');
if (logActivity) {
logActivity(0, userId, 'API_CLIENT_SYNC_TASK',
`Локальная задача ${taskId} ${existingTask ? 'обновлена' : 'создана'} в ${baseUrl}. Новый ID: ${result.taskId}`);
}
res.json({
success: true,
message: `Задача успешно ${existingTask ? 'обновлена' : 'создана'} в целевой системе`,
data: {
sync_type: result.action,
original_task_id: taskId,
target_task_id: result.taskId,
target_service: baseUrl,
synced_files: syncedFiles,
assignees: [], // исполнители не передаём
warnings: warnings,
source: 'local'
}
});
} catch (error) {
console.error('❌ Ошибка синхронизации задачи:', error.message);
let errorMessage = 'Ошибка синхронизации задачи';
let statusCode = 500;
if (error.response) {
if (error.response.status === 401) {
errorMessage = 'Неверный API ключ для целевого сервиса';
statusCode = 401;
} else if (error.response.status === 403) {
errorMessage = 'Нет прав для создания/обновления задачи в целевом сервисе';
statusCode = 403;
} else if (error.response.status === 404) {
errorMessage = 'Целевой эндпоинт не найден';
statusCode = 404;
} else {
errorMessage = error.response.data?.error || `Ошибка сервера: ${error.response.status}`;
statusCode = error.response.status;
}
} else if (error.code === 'ECONNREFUSED') {
errorMessage = 'Целевой сервис недоступен';
statusCode = 503;
} else if (error.code === 'ETIMEDOUT') {
errorMessage = 'Превышено время ожидания ответа';
statusCode = 504;
}
res.status(statusCode).json({ error: errorMessage, details: error.message });
}
});
/**
* POST /api/client/tasks/:taskId/files - Загрузить файлы в задачу
*/
@@ -912,20 +673,4 @@ module.exports = function(app, db, upload) {
app.use(router);
console.log('✅ API клиент для внешних сервисов подключен');
};
// Вспомогательная функция для получения файлов локальной задачи
async function getLocalTaskFiles(taskId) {
return new Promise((resolve, reject) => {
db.all(`
SELECT tf.*, u.name as user_name
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) reject(err);
else resolve(files || []);
});
});
}
};