FASHIONISTAR CI/CD
🔄 Celery Queues Deploy: 4aae6106b1530c3cc7d7d64f5e3c3e1d5015691d [GitHub Actions]
27c799c
Raw
History Blame
15.7 kB
# 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/<domain>/.
# 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 <access_token>`.\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)