import logging from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView # Import engine and vendors to ensure default registration import verifier.vendors # noqa: F401 from verifier.engine import engine, registry from .models import VerificationTransaction, VendorAttemptLog from .serializers import ( VerifyDocumentRequestSerializer, VerificationTransactionSerializer, ) logger = logging.getLogger(__name__) def mask_sensitive_payload(payload: dict) -> dict: """Mask sensitive inputs like full aadhaar numbers or passwords for audit safety.""" masked = dict(payload) sensitive_keys = ["aadhaar_number", "aadhaar", "otp", "password", "client_secret"] for key in sensitive_keys: if key in masked and isinstance(masked[key], str): val = masked[key] if len(val) > 4: masked[key] = f"{val[:2]}****{val[-2:]}" else: masked[key] = "****" return masked class DocumentVerifyView(APIView): """ POST /api/verify/ Verifies a document using the vendor waterfall failover engine. If the first vendor encounters an error/failure, the engine automatically cascades to the next available vendor. """ def post(self, request, *args, **kwargs): serializer = VerifyDocumentRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"is_success": False, "errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) doc_type = serializer.validated_data["document_type"] input_data = serializer.validated_data["data"] preferred_vendor = serializer.validated_data.get("preferred_vendor") files = request.FILES if hasattr(request, "FILES") else None # Execute waterfall verification engine_result = engine.verify( document_type=doc_type, data=input_data, files=files, preferred_vendor=preferred_vendor, ) # Persist transaction and audit attempts in DB safe_input = mask_sensitive_payload(input_data) transaction = VerificationTransaction.objects.create( document_type=doc_type, status=engine_result.status.value, successful_vendor=engine_result.successful_vendor, input_payload=safe_input, final_data=engine_result.data if engine_result.is_success else {}, attempts_count=len(engine_result.attempts), total_latency_ms=engine_result.total_latency_ms, ) # Persist attempt logs for attempt in engine_result.attempts: VendorAttemptLog.objects.create( transaction=transaction, attempt_number=attempt.get("attempt_number", 1), vendor_name=attempt.get("vendor_name", "unknown"), status=attempt.get("status", "UNKNOWN"), is_success=attempt.get("is_success", False), data=attempt.get("data", {}), raw_response=attempt.get("raw_response"), error_code=attempt.get("error_code"), error_message=attempt.get("error_message"), latency_ms=attempt.get("latency_ms", 0.0), ) response_payload = { "reference_id": str(transaction.reference_id), "is_success": engine_result.is_success, "status": engine_result.status.value, "document_type": doc_type, "successful_vendor": engine_result.successful_vendor, "data": engine_result.data, "attempts_count": len(engine_result.attempts), "attempts": engine_result.attempts, "error_message": engine_result.error_message, "total_latency_ms": round(engine_result.total_latency_ms, 2), } # Store complete client response payload in database record transaction.response_payload = response_payload transaction.save(update_fields=["response_payload"]) http_status = status.HTTP_200_OK if engine_result.is_success else status.HTTP_422_UNPROCESSABLE_ENTITY return Response(response_payload, status=http_status) class VendorListView(APIView): """ GET /api/vendors/ Lists all active verification vendors registered with the engine in priority order. """ def get(self, request, *args, **kwargs): vendors = registry.get_all_vendors() vendor_data = [ { "name": v.name, "display_name": getattr(v, "display_name", v.name), "priority": v.priority, "is_active": v.is_active, "supported_documents": v.supported_documents, } for v in vendors ] return Response({"count": len(vendor_data), "vendors": vendor_data}) class TransactionDetailView(APIView): """ GET /api/transactions// Fetch complete audit trail and fallback logs for a transaction. """ def get(self, request, reference_id, *args, **kwargs): try: tx = VerificationTransaction.objects.prefetch_related("attempts").get(reference_id=reference_id) except VerificationTransaction.DoesNotExist: return Response( {"error": f"Transaction '{reference_id}' not found"}, status=status.HTTP_404_NOT_FOUND, ) serializer = VerificationTransactionSerializer(tx) return Response(serializer.data) class TransactionListView(APIView): """ GET /api/transactions/ Lists recent verification transactions (up to 50) for dashboard/portal monitoring. """ def get(self, request, *args, **kwargs): limit = min(int(request.query_params.get("limit", 25)), 50) txs = VerificationTransaction.objects.prefetch_related("attempts").all()[:limit] serializer = VerificationTransactionSerializer(txs, many=True) return Response({"count": len(serializer.data), "results": serializer.data}) from django.views.generic import TemplateView class PortalView(TemplateView): """ Renders the interactive testing & inspection studio for all verification features. """ template_name = "verifier/portal.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) vendors = registry.get_all_vendors() context["vendors"] = vendors context["active_vendor_count"] = sum(1 for v in vendors if v.is_active) context["recent_transactions"] = VerificationTransaction.objects.prefetch_related("attempts").all()[:10] return context