documentverification/verifier/views.py
2026-10-15 19:42:15 +05:30

142 lines
5.3 KiB
Python

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),
}
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/<uuid:reference_id>/
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)