Files
aiform_prod/backend/app/api/documents_draft_open.py
2026-02-21 22:08:30 +03:00

133 lines
4.7 KiB
Python

"""
Documents draft-open endpoint
This file provides a single, isolated endpoint to fetch the documents list
and minimal claim metadata for a given claim_id. It is implemented as a
separate router to avoid touching existing document/claim routes.
"""
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import RedirectResponse
from ..config import settings
import logging
import json
from typing import Any, Dict
from ..services.database import db
router = APIRouter(prefix="/api/v1/documents-draft", tags=["DocumentsDraft"])
logger = logging.getLogger(__name__)
@router.get("/open/{claim_id}")
async def open_documents_draft(claim_id: str):
"""
Return minimal draft info focused on documents for the given claim_id.
Response:
{
"success": True,
"claim_id": "...",
"session_token": "...",
"status_code": "...",
"documents_required": [...],
"documents_meta": [...],
"documents_count": 3,
"created_at": "...",
"updated_at": "..."
}
"""
try:
query = """
SELECT
id,
payload->>'claim_id' AS claim_id,
session_token,
status_code,
payload->'documents_required' AS documents_required,
payload->'documents_meta' AS documents_meta,
created_at,
updated_at
FROM clpr_claims
WHERE (payload->>'claim_id' = $1 OR id::text = $1)
ORDER BY updated_at DESC
LIMIT 1
"""
row = await db.fetch_one(query, claim_id)
if not row:
raise HTTPException(status_code=404, detail=f"Draft not found: {claim_id}")
# Normalize JSONB fields which may be strings
def parse_json_field(val: Any):
if val is None:
return []
if isinstance(val, str):
try:
return json.loads(val)
except Exception:
return []
return val if isinstance(val, list) else []
documents_required = parse_json_field(row.get("documents_required"))
documents_meta = parse_json_field(row.get("documents_meta"))
result = {
"success": True,
"claim_id": row.get("claim_id") or str(row.get("id")),
"session_token": row.get("session_token"),
"status_code": row.get("status_code"),
"documents_required": documents_required,
"documents_meta": documents_meta,
"documents_count": len(documents_required),
"created_at": row.get("created_at").isoformat() if row.get("created_at") else None,
"updated_at": row.get("updated_at").isoformat() if row.get("updated_at") else None,
}
return result
except HTTPException:
raise
except Exception as e:
logger.exception("Failed to open documents draft")
raise HTTPException(status_code=500, detail=f"Error opening documents draft: {str(e)}")
@router.get("/open/launch/{claim_id}")
async def launch_documents_draft(
claim_id: str,
target: str = Query("miniapp", description="Where to open: 'miniapp' or 'max'"),
bot_name: str | None = Query(None, description="MAX bot name (required if target=max)"),
):
"""
Convenience launcher:
- target=miniapp (default) -> redirects to our miniapp URL with claim_id
https://miniapp.clientright.ru/hello?claim_id=...
- target=max -> redirects to MAX deep link:
https://max.ru/{bot_name}?startapp={claim_id}
This endpoint only redirects; it does not change persisted data.
"""
try:
# ensure claim exists
query = "SELECT 1 FROM clpr_claims WHERE (payload->>'claim_id' = $1 OR id::text = $1) LIMIT 1"
row = await db.fetch_one(query, claim_id)
if not row:
raise HTTPException(status_code=404, detail=f"Draft not found: {claim_id}")
if target == "max":
bot = bot_name or getattr(settings, "MAX_BOT_NAME", None)
if not bot:
raise HTTPException(status_code=400, detail="bot_name is required when target=max")
# claim_id is UUID with allowed chars (hex + hyphens) - OK for startapp
url = f"https://max.ru/{bot}?startapp={claim_id}"
return RedirectResponse(url)
else:
# default: open miniapp directly (hosted at /hello)
url = f"https://miniapp.clientright.ru/hello?claim_id={claim_id}"
return RedirectResponse(url)
except HTTPException:
raise
except Exception as e:
logger.exception("Failed to launch documents draft")
raise HTTPException(status_code=500, detail=f"Error launching documents draft: {str(e)}")