""" 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)}")