api-client
This commit is contained in:
142
api-client.js
142
api-client.js
@@ -445,7 +445,7 @@ module.exports = function(app, db, upload) {
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/client/tasks/:taskId/sync - Синхронизировать задачу с целевым сервисом
|
||||
* POST /api/client/tasks/:taskId/sync - Синхронизировать ЛОКАЛЬНУЮ задачу с целевым сервисом
|
||||
*/
|
||||
router.post('/api/client/tasks/:taskId/sync', requireAuth, async (req, res) => {
|
||||
const { taskId } = req.params;
|
||||
@@ -455,15 +455,9 @@ module.exports = function(app, db, upload) {
|
||||
target_api_key,
|
||||
sync_files = true
|
||||
} = req.body;
|
||||
const { connection_id } = req.query;
|
||||
const userId = req.session.user.id;
|
||||
|
||||
if (!connection_id && !req.session.clientConnections) {
|
||||
return res.status(400).json({
|
||||
error: 'Не указан источник (текущее подключение)'
|
||||
});
|
||||
}
|
||||
|
||||
// Определяем целевой сервис
|
||||
let targetUrl = target_api_url;
|
||||
let targetKey = target_api_key;
|
||||
|
||||
@@ -479,40 +473,43 @@ module.exports = function(app, db, upload) {
|
||||
});
|
||||
}
|
||||
|
||||
const baseUrl = targetUrl.replace(/\/$/, '');
|
||||
|
||||
try {
|
||||
const baseUrl = targetUrl.replace(/\/$/, '');
|
||||
const sourceConnection = req.session.clientConnections[connection_id];
|
||||
// 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 (!sourceConnection) {
|
||||
return res.status(400).json({ error: 'Исходное подключение не найдено' });
|
||||
if (!localTask) {
|
||||
return res.status(404).json({ error: 'Локальная задача не найдена' });
|
||||
}
|
||||
|
||||
// 1. Получаем исходную задачу
|
||||
const sourceResponse = await axios.get(
|
||||
`${sourceConnection.url}/api/external/tasks/${taskId}`,
|
||||
{
|
||||
headers: {
|
||||
'X-API-Key': sourceConnection.api_key
|
||||
},
|
||||
timeout: 10000
|
||||
}
|
||||
);
|
||||
// Преобразуем в формат, аналогичный внешнему 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)
|
||||
};
|
||||
|
||||
if (!sourceResponse.data || !sourceResponse.data.success) {
|
||||
return res.status(404).json({ error: 'Исходная задача не найдена' });
|
||||
}
|
||||
|
||||
const sourceTask = sourceResponse.data.task;
|
||||
|
||||
// 2. Проверяем, существует ли задача в целевой системе (поиск по ID)
|
||||
let existingTask = null;
|
||||
try {
|
||||
const checkResponse = await axios.get(
|
||||
`${baseUrl}/api/external/tasks/${taskId}`,
|
||||
{
|
||||
headers: {
|
||||
'X-API-Key': targetKey
|
||||
},
|
||||
headers: { 'X-API-Key': targetKey },
|
||||
timeout: 5000
|
||||
}
|
||||
);
|
||||
@@ -534,7 +531,7 @@ module.exports = function(app, db, upload) {
|
||||
title: sourceTask.title,
|
||||
description: sourceTask.description,
|
||||
due_date: sourceTask.due_date,
|
||||
task_type: sourceTask.task_type || 'regular'
|
||||
task_type: sourceTask.task_type
|
||||
};
|
||||
|
||||
const updateResponse = await axios.put(
|
||||
@@ -550,22 +547,17 @@ module.exports = function(app, db, upload) {
|
||||
);
|
||||
|
||||
if (!updateResponse.data || !updateResponse.data.success) {
|
||||
return res.status(500).json({
|
||||
error: 'Не удалось обновить задачу в целевом сервисе'
|
||||
});
|
||||
return res.status(500).json({ error: 'Не удалось обновить задачу в целевом сервисе' });
|
||||
}
|
||||
|
||||
result = {
|
||||
taskId: taskId,
|
||||
action: 'updated'
|
||||
};
|
||||
result = { taskId: taskId, action: 'updated' };
|
||||
} else {
|
||||
// 4. Создаем новую задачу (без префикса "Копия:")
|
||||
// 4. Создаём новую задачу
|
||||
const taskData = {
|
||||
title: sourceTask.title,
|
||||
description: sourceTask.description || '',
|
||||
description: sourceTask.description,
|
||||
due_date: sourceTask.due_date,
|
||||
task_type: sourceTask.task_type || 'regular'
|
||||
task_type: sourceTask.task_type
|
||||
};
|
||||
|
||||
const createResponse = await axios.post(
|
||||
@@ -581,9 +573,7 @@ module.exports = function(app, db, upload) {
|
||||
);
|
||||
|
||||
if (!createResponse.data || !createResponse.data.success) {
|
||||
return res.status(500).json({
|
||||
error: 'Не удалось создать задачу в целевом сервисе'
|
||||
});
|
||||
return res.status(500).json({ error: 'Не удалось создать задачу в целевом сервисе' });
|
||||
}
|
||||
|
||||
result = {
|
||||
@@ -596,21 +586,17 @@ module.exports = function(app, db, upload) {
|
||||
if (sync_files && sourceTask.files && sourceTask.files.length > 0) {
|
||||
for (const file of sourceTask.files) {
|
||||
try {
|
||||
// Скачиваем файл из источника
|
||||
const fileResponse = await axios({
|
||||
method: 'GET',
|
||||
url: `${sourceConnection.url}/api/external/tasks/${taskId}/files/${file.id}/download`,
|
||||
headers: {
|
||||
'X-API-Key': sourceConnection.api_key
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
timeout: 30000
|
||||
});
|
||||
// Читаем файл с диска
|
||||
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', Buffer.from(fileResponse.data), {
|
||||
filename: file.filename || file.original_name || 'file',
|
||||
formData.append('files', fileBuffer, {
|
||||
filename: fileName,
|
||||
contentType: file.file_type || 'application/octet-stream'
|
||||
});
|
||||
|
||||
@@ -629,25 +615,26 @@ module.exports = function(app, db, upload) {
|
||||
);
|
||||
|
||||
syncedFiles.push({
|
||||
original_name: file.filename || file.original_name,
|
||||
original_name: fileName,
|
||||
success: uploadResponse.data && uploadResponse.data.success
|
||||
});
|
||||
} catch (fileError) {
|
||||
console.error(`❌ Ошибка синхронизации файла:`, fileError.message);
|
||||
syncedFiles.push({
|
||||
original_name: file.filename || file.original_name,
|
||||
original_name: file.original_name || 'unknown',
|
||||
success: false,
|
||||
error: fileError.message
|
||||
});
|
||||
warnings.push(`Не удалось синхронизировать файл: ${file.filename || file.original_name}`);
|
||||
warnings.push(`Не удалось синхронизировать файл: ${file.original_name || file.filename}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Логируем действие
|
||||
const { logActivity } = require('./database');
|
||||
if (logActivity) {
|
||||
logActivity(0, userId, 'API_CLIENT_SYNC_TASK',
|
||||
`${existingTask ? 'Обновлена' : 'Создана'} задача ${taskId} из ${sourceConnection.url} в ${baseUrl}. ${existingTask ? 'Обновление' : 'Новый ID: ' + result.taskId}`);
|
||||
`Локальная задача ${taskId} ${existingTask ? 'обновлена' : 'создана'} в ${baseUrl}. Новый ID: ${result.taskId}`);
|
||||
}
|
||||
|
||||
res.json({
|
||||
@@ -659,17 +646,17 @@ module.exports = function(app, db, upload) {
|
||||
target_task_id: result.taskId,
|
||||
target_service: baseUrl,
|
||||
synced_files: syncedFiles,
|
||||
assignees: sourceTask.assignments || [],
|
||||
warnings: warnings
|
||||
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 ключ для целевого сервиса';
|
||||
@@ -678,7 +665,7 @@ module.exports = function(app, db, upload) {
|
||||
errorMessage = 'Нет прав для создания/обновления задачи в целевом сервисе';
|
||||
statusCode = 403;
|
||||
} else if (error.response.status === 404) {
|
||||
errorMessage = 'Исходная задача не найдена';
|
||||
errorMessage = 'Целевой эндпоинт не найден';
|
||||
statusCode = 404;
|
||||
} else {
|
||||
errorMessage = error.response.data?.error || `Ошибка сервера: ${error.response.status}`;
|
||||
@@ -692,10 +679,7 @@ module.exports = function(app, db, upload) {
|
||||
statusCode = 504;
|
||||
}
|
||||
|
||||
res.status(statusCode).json({
|
||||
error: errorMessage,
|
||||
details: error.message
|
||||
});
|
||||
res.status(statusCode).json({ error: errorMessage, details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -928,4 +912,20 @@ 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 || []);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user