initial commit

This commit is contained in:
root
2026-05-13 14:20:41 +00:00
commit 6e178d2012
6022 changed files with 399872 additions and 0 deletions
+178
View File
@@ -0,0 +1,178 @@
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,
},
});
}
// Pass-through для не-успешных кодов (waiting/fail) от universal result-workflow (n8n).
// n8n возвращает {success:{code:'fail'|'waiting', message}} — фронт ждёт success на корне.
// Без этого блока success теряется в spread внутри data.result, и фронт крутится
// в "Рендерит..." до таймаута вместо показа реальной ошибки kie.ai.
if (responseData?.success && typeof responseData.success === 'object'
&& responseData.success.code && responseData.success.code !== 'success') {
console.log('[result] pass-through non-success:', JSON.stringify(responseData.success));
return res.json({ success: responseData.success });
}
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;