feat: Session persistence with Redis + Draft management fixes
- Implement session management API (/api/v1/session/create, verify, logout) - Add session restoration from localStorage on page reload - Fix session_id priority when loading drafts (use current, not old from DB) - Add unified_id and claim_id to wizard payload sent to n8n - Add Docker volume for frontend HMR (Hot Module Replacement) - Add comprehensive session logging for debugging Components updated: - backend/app/api/session.py (NEW) - Session management endpoints - backend/app/main.py - Include session router - frontend/src/components/form/Step1Phone.tsx v2.0 - Create session after SMS - frontend/src/pages/ClaimForm.tsx v3.8 - Session restoration & priority fix - frontend/src/components/form/StepWizardPlan.tsx v1.4 - Add unified_id/claim_id - docker-compose.yml - Add frontend volume for live reload Session flow: 1. User verifies phone -> session created in Redis (24h TTL) 2. session_token saved to localStorage 3. Page reload -> session restored automatically 4. Draft selected -> current session_id used (not old from DB) 5. Wizard submit -> unified_id, claim_id, session_id sent to n8n 6. Logout -> session removed from Redis & localStorage Fixes: - Session token not persisting after page reload - unified_id missing in n8n webhook payload - Old session_id from draft overwriting current session - Frontend changes requiring container rebuild
This commit is contained in:
77
docs/CODE4_FIXED.js
Normal file
77
docs/CODE4_FIXED.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// n8n Code node (Run Once) — prepare object for Redis
|
||||
const items = $input.all();
|
||||
|
||||
// 1) Найти первый подходящий элемент с parsed.obj
|
||||
let main = null;
|
||||
for (const it of items) {
|
||||
const j = it.json;
|
||||
if (!j) continue;
|
||||
// возможные места
|
||||
if (j.parsed && j.parsed.obj) { main = j.parsed.obj; break; }
|
||||
if (j.parsed && j.parsed.ok && j.parsed.obj) { main = j.parsed.obj; break; }
|
||||
if (j.output) {
|
||||
// если output — строка JSON
|
||||
try {
|
||||
const parsed = JSON.parse(j.output);
|
||||
if (parsed && parsed.wizard_plan) { main = parsed; break; }
|
||||
} catch (e) {}
|
||||
}
|
||||
if (j.json && j.json.wizard_plan) { main = j.json; break; }
|
||||
}
|
||||
if (!main) {
|
||||
// последний шанс: взять items[0].json
|
||||
main = items[0] ? (items[0].json || items[0]) : null;
|
||||
}
|
||||
if (!main) {
|
||||
throw new Error('Не удалось найти parsed.obj в входных данных');
|
||||
}
|
||||
|
||||
// 2) Гарантии структуры
|
||||
main.wizard_plan = main.wizard_plan || {};
|
||||
main.coverage_report = main.coverage_report || {};
|
||||
main.coverage_report.docs_received = main.coverage_report.docs_received || [];
|
||||
main.wizard_plan.risks = main.wizard_plan.risks || ['DOCS_STATUS_UNKNOWN','EXPECTATION_UNSET'];
|
||||
main.wizard_plan.deadlines = main.wizard_plan.deadlines || [
|
||||
{ type: 'USER_UPLOAD_TTL', duration_hours: 48 },
|
||||
{ type: 'USER_APPROVAL_TTL', duration_hours: 24 }
|
||||
];
|
||||
|
||||
// 3) Добавить примерный документ (state/cities) — если ещё нет такого id
|
||||
const exampleId = 'example_state_cities_json';
|
||||
const already = main.coverage_report.docs_received.find(d => d.id === exampleId);
|
||||
if (!already) {
|
||||
const exampleDoc = {
|
||||
id: exampleId,
|
||||
name: 'state_cities_example.json',
|
||||
type: 'application/json',
|
||||
uploaded_at: new Date().toISOString(),
|
||||
content: {
|
||||
state: 'California',
|
||||
cities: ['Los Angeles', 'San Francisco', 'San Diego']
|
||||
}
|
||||
};
|
||||
main.coverage_report.docs_received.push(exampleDoc);
|
||||
}
|
||||
|
||||
// 4) session token / key
|
||||
// Получаем session_token из разных источников (приоритет: Edit Fields11 > Redis Trigger)
|
||||
const sessionToken = $('Edit Fields11').first().json.session_token
|
||||
|| $('Redis Trigger').first().json.message.session_id
|
||||
|| null;
|
||||
|
||||
// Если session_token недоступен, генерируем временный ключ
|
||||
if (!sessionToken) {
|
||||
console.warn('⚠️ session_token не найден, используем временный ключ');
|
||||
}
|
||||
|
||||
// Используем session_token для Redis ключа (claim_id будет сгенерирован позже)
|
||||
const redisKey = `ocr_events:${sessionToken || 'temp-' + Date.now()}`;
|
||||
|
||||
// 5) Возвращаем объект для следующего Redis node
|
||||
return [{
|
||||
json: {
|
||||
redis_key: redisKey,
|
||||
redis_value: main
|
||||
}
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user