""" Views for KYC API. Authentication is handled by GRAPHENE MIDDLEWARE in settings.py """ import os from datetime import timedelta from django.db.models import Count, Q from django.http import HttpResponse from django.utils import timezone from graphene_django.views import GraphQLView from .graphql_middleware import UserJWTMiddleware from .models import KYCProfile class UserGraphQLView(GraphQLView): """User endpoint - requires ID Token.""" def __init__(self, *args, **kwargs): kwargs['middleware'] = [UserJWTMiddleware()] super().__init__(*args, **kwargs) class M2MGraphQLView(GraphQLView): """M2M endpoint - no authentication (internal network only).""" pass class OptionalUserJWTMiddleware: """Middleware that optionally extracts user_id but doesn't fail if missing.""" def resolve(self, next, root, info, **args): request = info.context # Try to extract user_id from Authorization header if present auth_header = request.META.get('HTTP_AUTHORIZATION', '') if auth_header.startswith('Bearer '): try: from .auth import validate_jwt_token token = auth_header.split(' ', 1)[1] payload = validate_jwt_token(token) if payload: request.user_id = payload.get('sub') except Exception: pass # Ignore auth errors - user just won't get full data return next(root, info, **args) class PublicGraphQLView(GraphQLView): """Public endpoint - optional auth for full data, no auth for teaser.""" def __init__(self, *args, **kwargs): # Use optional auth middleware that doesn't fail on missing token kwargs['middleware'] = [OptionalUserJWTMiddleware()] super().__init__(*args, **kwargs) def metrics_view(request): """Prometheus metrics endpoint for KYC monitoring status.""" stale_days = int(os.getenv("KYC_STALE_DAYS", "90")) cutoff = timezone.now() - timedelta(days=stale_days) stats = KYCProfile.objects.filter(workflow_status="active").aggregate( total=Count("id"), stale=Count("id", filter=Q(updated_at__lt=cutoff) | Q(updated_at__isnull=True)), fresh=Count("id", filter=Q(updated_at__gte=cutoff)), ) total = int(stats.get("total") or 0) stale = int(stats.get("stale") or 0) fresh = int(stats.get("fresh") or 0) ratio = 0.0 if total == 0 else stale / total payload = ( "# HELP kyc_monitoring_total Total active profiles in monitoring\n" "# TYPE kyc_monitoring_total gauge\n" f"kyc_monitoring_total {total}\n" "# HELP kyc_monitoring_stale Stale profiles (no refresh within window)\n" "# TYPE kyc_monitoring_stale gauge\n" f"kyc_monitoring_stale {stale}\n" "# HELP kyc_monitoring_fresh Fresh profiles (refreshed within window)\n" "# TYPE kyc_monitoring_fresh gauge\n" f"kyc_monitoring_fresh {fresh}\n" "# HELP kyc_monitoring_stale_ratio Stale / total ratio\n" "# TYPE kyc_monitoring_stale_ratio gauge\n" f"kyc_monitoring_stale_ratio {ratio}\n" ) return HttpResponse(payload, content_type="text/plain; version=0.0.4; charset=utf-8")