Files
uno-click/bff/routes/scenario.routes.js
2026-05-13 14:20:41 +00:00

152 lines
4.6 KiB
JavaScript

import { Router } from 'express';
import { authRequired } from '../middleware/authRequired.js';
import { csrfRequired } from '../middleware/csrfRequired.js';
import * as scenarioService from '../services/scenario.service.js';
const router = Router();
/**
* POST /api/scenario/:scenarioId/start
* Запуск сценария. body должен быть пустым {}.
*/
router.post('/:scenarioId/start', authRequired, csrfRequired, async (req, res, next) => {
try {
const { scenarioId } = req.params;
const input = req.body;
// 1) проверить, что пользователь имеет право на этот scenarioId
await scenarioService.assertUserCanStartScenario({
userId: req.user.id,
scenarioId,
});
// 2) создать generation и вызвать n8n
const generation = await scenarioService.startScenario({
userId: req.user.id,
scenarioId,
input,
user: req.user,
});
res.status(202).json({
ok: true,
generationUuid: generation.generationUuid,
status: generation.status,
currentStepId: generation.currentStepId,
});
} catch (err) {
next(err);
}
});
/**
* POST /api/scenario/:scenarioId/step/:stepId
* Выполнение шага сценария. body должен соответствовать input_schema из БД.
*/
router.post('/:scenarioId/step/:stepId', authRequired, csrfRequired, async (req, res, next) => {
try {
const { scenarioId, stepId } = req.params;
const input = req.body;
// 1) проверить, что пользователь может выполнять этот шаг
await scenarioService.assertUserCanExecuteStep({
userId: req.user.id,
scenarioId,
stepId,
});
// 2) выполнить шаг (с валидацией input_schema)
const result = await scenarioService.executeStep({
userId: req.user.id,
scenarioId,
stepId,
input,
user: req.user,
});
res.status(202).json({
ok: true,
generationUuid: result.generationUuid,
stepState: result.stepState,
nextStepId: result.nextStepId,
});
} catch (err) {
next(err);
}
});
/**
* POST /api/scenario/:scenarioId/step/:stepId/record
* Создать запись generation_step и вернуть её ID (для загрузки файлов).
* Используется когда нужно загрузить файл ДО выполнения шага.
*/
router.post('/:scenarioId/step/:stepId/record', authRequired, csrfRequired, async (req, res, next) => {
try {
const { scenarioId, stepId } = req.params;
const input = req.body;
const userId = req.user.id;
// Проверка прав
await scenarioService.assertUserCanExecuteStep({
userId,
scenarioId,
stepId,
});
// Найти активную generation
const { pool } = await import('../db.js');
const genQuery = `
SELECT generation_uuid, current_step_id, status
FROM uno_bff.generations
WHERE user_id = $1 AND scenario_id = $2 AND status IN ('running', 'waiting_for_input')
ORDER BY created_at DESC
LIMIT 1
`;
const genResult = await pool.query(genQuery, [userId, scenarioId]);
const generation = genResult.rows[0];
if (!generation) {
return res.status(400).json({
ok: false,
error: 'GENERATION_NOT_FOUND',
message: `No active generation found for scenario '${scenarioId}'`,
});
}
// Найти шаг сценария для получения step_order
const step = await scenarioService.getScenarioStep(scenarioId, stepId);
if (!step) {
return res.status(404).json({
ok: false,
error: 'STEP_NOT_FOUND',
message: `Step '${stepId}' not found`,
});
}
// Создать/обновить запись generation_step
const stepQuery = `
INSERT INTO uno_bff.generation_steps (generation_uuid, scenario_id, step_id, step_order, status, request_payload)
VALUES ($1, $2, $3, $4, 'running', $5)
ON CONFLICT (generation_uuid, step_id) DO UPDATE
SET status = 'running', request_payload = $5, updated_at = now()
RETURNING id
`;
const stepResult = await pool.query(stepQuery, [
generation.generation_uuid,
scenarioId,
stepId,
step.step_order,
JSON.stringify(input),
]);
res.status(201).json({
ok: true,
stepRecordId: stepResult.rows[0].id,
generationUuid: generation.generation_uuid,
});
} catch (err) {
next(err);
}
});
export default router;