# backend/ninja_api.py """ Fashionistar — Central Django-Ninja API (Async). Architecture: All high-performance async endpoints are registered here as a SINGLE NinjaAPI instance. Never instantiate NinjaAPI anywhere else in the project — import this module's `ninja_api` object. Mounted at: /api/v1/ninja/ (see backend/urls.py) Authentication: All routes use JWT Bearer (AsyncJWTAuth) by default. Public endpoints explicitly override with auth=None. Router registry ← SINGLE source-of-truth: /api/v1/ninja/common/ → apps/common /api/v1/ninja/client/ → apps/client /api/v1/ninja/vendor/ → apps/vendor /api/v1/ninja/notifications/ → apps/notification /api/v1/ninja/support/ → apps/support /api/v1/ninja/catalog/ → apps/catalog /api/v1/ninja/products/ → apps/product /api/v1/ninja/cart/ → apps/cart /api/v1/ninja/orders/ → apps/order /api/v1/ninja/wallet/ → apps/wallet (GET: dashboard, balance) /api/v1/ninja/wallet/company/payout/ → apps/wallet (POST: company commission payout) /api/v1/ninja/transactions/ → apps/transactions /api/v1/ninja/payments/ → apps/payment /api/v1/ninja/kyc/ → apps/kyc /api/v1/ninja/measurements/ → apps/measurements /api/v1/ninja/scheduler/ → apps/scheduler /api/v1/ninja/devops/ → apps/devops /api/v1/ninja/ai/ → apps/ai (scan status, recommendations, health) /api/v1/ninja/ai/health/ → AI sub-system health check (public) /api/v1/ninja/ai/scan/{id}/status/ → Body scan session polling /api/v1/ninja/ai/profiles/{id}/recommend/ → Outfit recommendations /api/v1/ninja/ai/products/{id}/size-advice/ → LLM size advice """ import logging from ninja import NinjaAPI from ninja.security import HttpBearer logger = logging.getLogger("application") # ── JWT Bearer Authentication ───────────────────────────────────────────────── class AsyncJWTAuth(HttpBearer): """ JWT Bearer authentication for Ninja endpoints. Validates the same SimpleJWT access token used by DRF. Returns the UnifiedUser instance so `request.auth` is the user object, with client_profile, vendor_profile, and kyc_submission pre-fetched. """ async def authenticate(self, request, token: str): try: from rest_framework_simplejwt.tokens import AccessToken from apps.authentication.models import UnifiedUser # Validate token decoded = AccessToken(token) user_id = decoded.get("user_id") if not user_id: return None # Async ORM lookup. Reverse OneToOne prefetch related profiles are hydrated here so # Ninja Async read downstream handlers can acces use request.auth.client_profile, # request.auth.vendor_profile, and request.auth.kyc_submission # without issuing another extra profile/KYC queries. return await ( UnifiedUser.objects.select_related( "client_profile", "vendor_profile", "kyc_submission", ) .aget(pk=user_id, is_active=True) ) except Exception as exc: logger.warning(" ninja_api.AsyncJWTAuth: failed to validate token: %s", exc) return None # ── Central Ninja API (singleton) ───────────────────────────────────────────── # # All Ninja endpoints MUST be mounted under /api/v1/ninja/ to: # 1. Maintain uniform v1 versioning across the whole API surface. # 2. Avoid URL collisions with DRF sync endpoints at /api/v1//. # 3. Make the async/sync split explicit for clients: # Reads → Ninja /api/v1/ninja/* # Writes → DRF /api/v1/* ninja_api = NinjaAPI( title="Fashionistar Async API", version="1.0.0", description=( "High-performance async endpoints for Fashionistar.\n\n" "All routes require `Authorization: Bearer `.\n" "Obtain tokens from the DRF auth endpoint: `POST /api/v1/auth/login/`." ), auth=AsyncJWTAuth(), urls_namespace="ninja", docs_url="/docs/", ) # ── Domain Router Registry ──────────────────────────────────────────────────── # One add_router call per domain — guard with try/except so a missing # optional router never crashes the other 13 domains. # Common reference-data domain: /api/v1/ninja/common/ try: from apps.common.apis.async_.reference_views import router as common_router ninja_api.add_router("/common/", common_router) logger.debug("✅ NinjaAPI: common router registered at /api/v1/ninja/common/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: common router FAILED to register: %s", exc) # Telemetry domain: /api/v1/ninja/common/telemetry/ # Receives Core Web Vitals from Next.js edge; auth via X-Internal-Token header. try: from apps.common.telemetry_api import telemetry_router ninja_api.add_router("/common/telemetry/", telemetry_router, url_name_prefix="telemetry") logger.debug("✅ NinjaAPI: telemetry router registered at /api/v1/ninja/common/telemetry/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: telemetry router FAILED to register: %s", exc) # Client domain: /api/v1/ninja/client/ try: from apps.client.apis.async_.dashboard_views import router as client_router ninja_api.add_router("/client/", client_router) logger.debug("✅ NinjaAPI: client router registered at /api/v1/ninja/client/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: client router FAILED to register: %s", exc) # Vendor domain: /api/v1/ninja/vendor/ try: from apps.vendor.apis.async_.dashboard_views import router as vendor_router ninja_api.add_router("/vendor/", vendor_router) logger.debug("✅ NinjaAPI: vendor router registered at /api/v1/ninja/vendor/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: vendor router FAILED to register: %s", exc) # Notification domain: /api/v1/ninja/notifications/ try: from apps.notification.apis.async_.notification_views import router as notification_router ninja_api.add_router("/notifications/", notification_router) logger.debug("✅ NinjaAPI: notification router registered at /api/v1/ninja/notifications/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: notification router FAILED to register: %s", exc) # Support domain: /api/v1/ninja/support/ try: from apps.support.apis.async_.support_views import router as support_router ninja_api.add_router("/support/", support_router) logger.debug("✅ NinjaAPI: support router registered at /api/v1/ninja/support/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: support router FAILED to register: %s", exc) # Catalog domain: /api/v1/ninja/catalog/ try: from apps.catalog.apis.async_.catalog_views import router as catalog_router ninja_api.add_router("/catalog/", catalog_router) logger.debug("✅ NinjaAPI: catalog router registered at /api/v1/ninja/catalog/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: catalog router FAILED to register: %s", exc) # Product domain: /api/v1/ninja/products/ and /api/v1/ninja/product/ try: from apps.product.apis.async_.product_views import router as product_router ninja_api.add_router("/products/", product_router, url_name_prefix="products") ninja_api.add_router("/product/", product_router, url_name_prefix="product") logger.debug("✅ NinjaAPI: product router registered at /api/v1/ninja/products/ and /api/v1/ninja/product/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: product router FAILED to register: %s", exc) # Order domain: /api/v1/ninja/orders/ try: from apps.order.apis.async_.order_views import router as order_router ninja_api.add_router("/orders/", order_router) logger.debug("✅ NinjaAPI: order router registered at /api/v1/ninja/orders/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: order router FAILED to register: %s", exc) # Wallet domain: /api/v1/ninja/wallet/ # Read-only dashboard + balance endpoints. try: from apps.wallet.apis.async_.wallet_views import router as wallet_router ninja_api.add_router("/wallet/", wallet_router) logger.debug("✅ NinjaAPI: wallet router registered at /api/v1/ninja/wallet/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: wallet router FAILED to register: %s", exc) # Wallet Mutations: /api/v1/ninja/wallet/ # High-security write endpoints — company commission payout (Double-Door secured). try: from apps.wallet.apis.async_.mutation_views import router as wallet_mutation_router ninja_api.add_router("/wallet/", wallet_mutation_router) logger.debug("✅ NinjaAPI: wallet mutation router registered (POST /api/v1/ninja/wallet/company/payout/)") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: wallet mutation router FAILED to register: %s", exc) # Transactions domain: /api/v1/ninja/transactions/ try: from apps.transactions.apis.async_.transaction_views import router as transaction_router ninja_api.add_router("/transactions/", transaction_router) logger.debug("✅ NinjaAPI: transactions router registered at /api/v1/ninja/transactions/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: transactions router FAILED to register: %s", exc) # Payment domain: /api/v1/ninja/payments/ try: from apps.payment.apis.async_.payment_views import router as payment_router ninja_api.add_router("/payments/", payment_router) logger.debug("✅ NinjaAPI: payment router registered at /api/v1/ninja/payments/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: payment router FAILED to register: %s", exc) # KYC domain: /api/v1/ninja/kyc/ try: from apps.kyc.apis.async_.kyc_views import router as kyc_router ninja_api.add_router("/kyc/", kyc_router) logger.debug("✅ NinjaAPI: kyc router registered at /api/v1/ninja/kyc/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: kyc router FAILED to register: %s", exc) # Measurements domain: /api/v1/ninja/measurements/ try: from apps.measurements.apis.async_.measurement_views import router as measurements_router ninja_api.add_router("/measurements/", measurements_router) logger.debug("✅ NinjaAPI: measurements router registered at /api/v1/ninja/measurements/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: measurements router FAILED to register: %s", exc) # Custom Order (Client) domain: /api/v1/ninja/client/custom-orders/ try: from apps.custom_order.apis.custom_order_views import client_custom_order_router ninja_api.add_router("/client/custom-orders/", client_custom_order_router) logger.debug("✅ NinjaAPI: client custom-orders router registered at /api/v1/ninja/client/custom-orders/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: client custom-orders router FAILED to register: %s", exc) # Custom Order (Vendor) domain: /api/v1/ninja/vendor/custom-orders/ try: from apps.custom_order.apis.custom_order_views import vendor_custom_order_router ninja_api.add_router("/vendor/custom-orders/", vendor_custom_order_router) logger.debug("✅ NinjaAPI: vendor custom-orders router registered at /api/v1/ninja/vendor/custom-orders/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: vendor custom-orders router FAILED to register: %s", exc) # Search domain: /api/v1/ninja/search/ try: from apps.search.apis.async_.search_views import router as search_router ninja_api.add_router("/search/", search_router) logger.debug("✅ NinjaAPI: search router registered at /api/v1/ninja/search/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: search router FAILED to register: %s", exc) # Scheduler domain: /api/v1/ninja/scheduler/ try: from apps.scheduler.apis.async_.scheduler_views import router as scheduler_router ninja_api.add_router("/scheduler/", scheduler_router) logger.debug("✅ NinjaAPI: scheduler router registered at /api/v1/ninja/scheduler/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: scheduler router FAILED to register: %s", exc) # DevOps domain: /api/v1/ninja/devops/ try: from apps.devops.apis.async_.devops_views import router as devops_router ninja_api.add_router("/devops/", devops_router) logger.debug("✅ NinjaAPI: devops router registered at /api/v1/ninja/devops/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: devops router FAILED to register: %s", exc) # AI Engine domain: /api/v1/ninja/ai/ try: from apps.ai.apis.async_api.ai_router import router as ai_router ninja_api.add_router("/ai/", ai_router) logger.debug("✅ NinjaAPI: AI engine router registered at /api/v1/ninja/ai/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: AI engine router FAILED to register: %s", exc) # Chatbot domain: /api/v1/ninja/chatbot/ try: # pyrefly: ignore [missing-import] from apps.chatbot.views.async_views import router as chatbot_router ninja_api.add_router("/chatbot/", chatbot_router) logger.debug("✅ NinjaAPI: chatbot router registered at /api/v1/ninja/chatbot/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: chatbot router FAILED to register: %s", exc) # App Standards domain: /api/v1/ninja/app-standards/ try: from apps.app_standards.views.async_views import router as app_standards_router ninja_api.add_router("/app-standards/", app_standards_router) logger.debug("✅ NinjaAPI: app-standards router registered at /api/v1/ninja/app-standards/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: app-standards router FAILED to register: %s", exc) # Analytics domain: /api/v1/ninja/analytics/ try: from apps.analytics.apis.async_.analytics_views import router as analytics_router ninja_api.add_router("/analytics/", analytics_router) logger.debug("✅ NinjaAPI: analytics router registered at /api/v1/ninja/analytics/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: analytics router FAILED to register: %s", exc) # Analytics dashboard: /api/v1/ninja/analytics/dashboard/ try: from apps.analytics.apis.async_.dashboard_views import router as analytics_dashboard_router ninja_api.add_router("/analytics/dashboard/", analytics_dashboard_router) logger.debug("✅ NinjaAPI: analytics dashboard router registered at /api/v1/ninja/analytics/dashboard/") except Exception as exc: # pragma: no cover logger.warning("ℹ️ NinjaAPI: analytics dashboard router FAILED to register: %s", exc)