91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
"""
|
|
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")
|