Files
crm.clientright.ru/ticket_form/backend/app/api/draft.py

199 lines
6.1 KiB
Python
Raw Normal View History

"""
Draft API Routes - Автосохранение драфтов форм
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict, Any
from datetime import datetime
import json
from ..services.database import db
import logging
router = APIRouter(prefix="/api/v1/draft", tags=["Draft"])
logger = logging.getLogger(__name__)
class DraftSaveRequest(BaseModel):
"""Запрос на сохранение драфта"""
session_id: str # Уникальный ID сессии пользователя
step: int # Текущий шаг формы (1, 2, 3)
data: Dict[str, Any] # Данные формы
user_agent: Optional[str] = None
ip_address: Optional[str] = None
@router.post("/save")
async def save_draft(request: DraftSaveRequest):
"""
Автосохранение драфта формы
Используется для аналитики:
- Где пользователи бросают заполнение
- Сколько времени проводят на каждом шаге
- Какие поля вызывают проблемы
"""
# Защита: валидация session_id
if not request.session_id or len(request.session_id) > 255:
raise HTTPException(status_code=400, detail="Invalid session_id")
# Защита: валидация step
if request.step not in [1, 2, 3]:
raise HTTPException(status_code=400, detail="Invalid step number")
try:
# Сериализуем данные в JSON
form_data_json = json.dumps(request.data, ensure_ascii=False)
# SQL для upsert (insert or update)
query = """
INSERT INTO claims_draft (
session_id,
current_step,
form_data,
user_agent,
ip_address,
created_at,
updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (session_id)
DO UPDATE SET
current_step = EXCLUDED.current_step,
form_data = EXCLUDED.form_data,
user_agent = EXCLUDED.user_agent,
ip_address = EXCLUDED.ip_address,
updated_at = EXCLUDED.updated_at
RETURNING id
"""
now = datetime.now()
result = await db.fetchval(
query,
request.session_id,
request.step,
form_data_json,
request.user_agent,
request.ip_address,
now,
now
)
logger.info(f"✅ Draft saved: session={request.session_id}, step={request.step}")
return {
"success": True,
"message": "Драфт сохранен",
"draft_id": result
}
except Exception as e:
logger.error(f"Draft save error: {e}")
# Не падаем с ошибкой - просто логируем
# Автосохранение не должно блокировать пользователя
return {
"success": False,
"message": "Ошибка сохранения драфта"
}
@router.get("/stats")
async def get_draft_stats():
"""
Статистика по драфтам
Показывает:
- Сколько людей бросают на каждом шаге
- Среднее время на шаге
- Количество драфтов за период
"""
try:
# Статистика по шагам
step_stats_query = """
SELECT
current_step,
COUNT(*) as count,
COUNT(DISTINCT session_id) as unique_users
FROM claims_draft
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY current_step
ORDER BY current_step
"""
step_stats = await db.fetch(step_stats_query)
# Общая статистика
total_drafts_query = """
SELECT COUNT(*) as total
FROM claims_draft
WHERE created_at >= NOW() - INTERVAL '7 days'
"""
total = await db.fetchval(total_drafts_query)
return {
"success": True,
"period": "last_7_days",
"total_drafts": total,
"by_step": [
{
"step": row["current_step"],
"count": row["count"],
"unique_users": row["unique_users"]
}
for row in step_stats
]
}
except Exception as e:
logger.error(f"Draft stats error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/list")
async def list_recent_drafts(limit: int = 50):
"""
Список последних драфтов
Для просмотра что люди заполняют
"""
try:
query = """
SELECT
id,
session_id,
current_step,
form_data,
created_at,
updated_at,
user_agent,
ip_address
FROM claims_draft
ORDER BY updated_at DESC
LIMIT $1
"""
drafts = await db.fetch(query, limit)
return {
"success": True,
"count": len(drafts),
"drafts": [
{
"id": row["id"],
"session_id": row["session_id"],
"step": row["current_step"],
"data": json.loads(row["form_data"]) if row["form_data"] else {},
"created_at": row["created_at"].isoformat(),
"updated_at": row["updated_at"].isoformat(),
"user_agent": row["user_agent"],
"ip_address": row["ip_address"]
}
for row in drafts
]
}
except Exception as e:
logger.error(f"Draft list error: {e}")
raise HTTPException(status_code=500, detail=str(e))