169 lines
5.4 KiB
Plaintext
169 lines
5.4 KiB
Plaintext
import { Router } from 'express';
|
||
import { authRequired } from '../middleware/authRequired.js';
|
||
import * as resultService from '../services/result.service.js';
|
||
import axios from 'axios';
|
||
import { env } from '../config/env.js';
|
||
|
||
const router = Router();
|
||
|
||
/**
|
||
* GET /api/result/:generationUuid
|
||
* Получить результат генерации
|
||
*/
|
||
router.get('/:generationUuid', authRequired, async (req, res, next) => {
|
||
try {
|
||
const { generationUuid } = req.params;
|
||
const userId = req.user.id;
|
||
|
||
// Получаем метаданные генерации из БД (только для получения scenarioId)
|
||
const generationMeta = await resultService.getGenerationMeta({ userId, generationUuid });
|
||
|
||
if (!generationMeta) {
|
||
return res.status(404).json({
|
||
error: 'NOT_FOUND',
|
||
message: 'Result not found or access denied',
|
||
});
|
||
}
|
||
|
||
// Вызываем n8n webhook для получения результата
|
||
const stepData = {};
|
||
|
||
const n8nUrl = 'https://n8n.uno-click.pip-test.ru/webhook/result';
|
||
const n8nResponse = await axios.post(n8nUrl, {
|
||
meta: { generationUuid, userId, scenarioId: generationMeta.scenarioId, stepData },
|
||
body: stepData
|
||
});
|
||
|
||
// Проверяем формат ответа от n8n
|
||
const n8nData = n8nResponse.data;
|
||
console.log('[result] n8n response:', JSON.stringify(n8nData, null, 2));
|
||
|
||
// n8n может вернуть данные в разных форматах
|
||
// Формат 1: { response: { body: { success: {...} } } }
|
||
// Формат 2: { success: {...} }
|
||
// Формат 3: [{ response: { body: { success: {...} } } }] - массив
|
||
let responseData = n8nData;
|
||
|
||
// Если массив - берём первый элемент
|
||
if (Array.isArray(n8nData) && n8nData.length > 0) {
|
||
responseData = n8nData[0];
|
||
}
|
||
|
||
// Если есть response.body - извлекаем
|
||
if (responseData?.response?.body) {
|
||
responseData = responseData.response.body;
|
||
}
|
||
|
||
console.log('[result] extracted responseData:', JSON.stringify(responseData, null, 2));
|
||
|
||
// Вариант 1: n8n вернул output_s3 в response_payload
|
||
if (responseData?.success?.response_payload?.output_s3) {
|
||
const s3Key = responseData.success.response_payload.output_s3.replace('s3://uno-click/', '');
|
||
const publicUrl = `/files/${s3Key}`;
|
||
|
||
return res.json({
|
||
ok: true,
|
||
result: {
|
||
generationUuid,
|
||
scenarioId: generationMeta.scenarioId,
|
||
scenarioName: generationMeta.scenarioName,
|
||
status: 'completed',
|
||
files: [{
|
||
contentType: 'video',
|
||
url: publicUrl,
|
||
}],
|
||
},
|
||
});
|
||
}
|
||
|
||
// Вариант 2: n8n вернул files массив с s3:// URL
|
||
if (responseData?.success?.files && Array.isArray(responseData.success.files)) {
|
||
const convertedFiles = responseData.success.files.map(file => {
|
||
// Конвертируем s3:// в /files/...
|
||
// Поддерживаем оба формата: { url: "s3://..." } и { output_s3: "s3://..." }
|
||
let fileUrl = file.url || file.output_s3;
|
||
|
||
if (fileUrl && fileUrl.startsWith('s3://uno-click/')) {
|
||
const s3Key = fileUrl.replace('s3://uno-click/', '');
|
||
return {
|
||
contentType: file.contentType,
|
||
url: `/files/${s3Key}`,
|
||
};
|
||
}
|
||
return file;
|
||
});
|
||
|
||
return res.json({
|
||
ok: true,
|
||
result: {
|
||
generationUuid,
|
||
scenarioId: generationMeta.scenarioId,
|
||
scenarioName: generationMeta.scenarioName,
|
||
status: 'completed',
|
||
files: convertedFiles,
|
||
},
|
||
});
|
||
}
|
||
|
||
// Вариант 3: success на верхнем уровне (для совместимости)
|
||
if (n8nData?.success?.files && Array.isArray(n8nData.success.files)) {
|
||
const convertedFiles = n8nData.success.files.map(file => {
|
||
if (file.url && file.url.startsWith('s3://uno-click/')) {
|
||
const s3Key = file.url.replace('s3://uno-click/', '');
|
||
return {
|
||
...file,
|
||
url: `/files/${s3Key}`,
|
||
};
|
||
}
|
||
return file;
|
||
});
|
||
|
||
return res.json({
|
||
ok: true,
|
||
result: {
|
||
generationUuid,
|
||
scenarioId: generationMeta.scenarioId,
|
||
scenarioName: generationMeta.scenarioName,
|
||
status: 'completed',
|
||
files: convertedFiles,
|
||
},
|
||
});
|
||
}
|
||
|
||
// Вариант 4: провайдер вернул fail с сообщением — пробрасываем наверх
|
||
if (responseData?.success?.code === 'fail') {
|
||
return res.json({
|
||
success: {
|
||
code: 'fail',
|
||
message: responseData.success.message || responseData.success.msg,
|
||
},
|
||
});
|
||
}
|
||
|
||
// Вариант 5: ошибка от n8n с code/message — пробрасываем наверх
|
||
if (responseData?.error?.code) {
|
||
return res.json({
|
||
error: {
|
||
code: responseData.error.code,
|
||
message: responseData.error.message,
|
||
},
|
||
});
|
||
}
|
||
|
||
res.json({
|
||
ok: true,
|
||
result: {
|
||
generationUuid,
|
||
scenarioId: generationMeta.scenarioId,
|
||
scenarioName: generationMeta.scenarioName,
|
||
...responseData,
|
||
},
|
||
});
|
||
} catch (err) {
|
||
console.error('[result] Error:', err);
|
||
next(err);
|
||
}
|
||
});
|
||
|
||
export default router;
|