""" OpenAPI Schema Extensions for drf-spectacular Custom extensions for JWT authentication and unified response format """ from drf_spectacular.extensions import OpenApiAuthenticationExtension from drf_spectacular.plumbing import build_bearer_security_scheme_object from drf_spectacular.utils import extend_schema, OpenApiResponse from rest_framework import status # Explicit tags we want to keep (from SPECTACULAR_SETTINGS) EXPLICIT_TAGS = { 'Authentication', 'Planner', 'Writer', 'System', 'Billing', 'Account', 'Automation', 'Linker', 'Optimizer', 'Publisher', 'Integration', 'Admin Billing', } def postprocess_schema_filter_tags(result, generator, request, public): """ Postprocessing hook to remove auto-generated tags and keep only explicit tags. This prevents duplicate tags from URL patterns (auth, planner, etc.) """ # First, filter tags from all paths (operations) if 'paths' in result: for path, methods in result['paths'].items(): for method, operation in methods.items(): if isinstance(operation, dict) and 'tags' in operation: # Explicitly exclude system webhook from tagging/docs grouping if '/system/webhook' in path: operation['tags'] = [] continue # Keep only explicit tags from the operation filtered_tags = [ tag for tag in operation['tags'] if tag in EXPLICIT_TAGS ] # If no explicit tags found, infer from path if not filtered_tags: if '/ping' in path or '/system/ping/' in path: filtered_tags = ['System'] # Health check endpoint elif '/auth/' in path or '/api/v1/auth/' in path: filtered_tags = ['Authentication'] elif '/planner/' in path or '/api/v1/planner/' in path: filtered_tags = ['Planner'] elif '/writer/' in path or '/api/v1/writer/' in path: filtered_tags = ['Writer'] elif '/system/' in path or '/api/v1/system/' in path: filtered_tags = ['System'] elif '/billing/' in path or '/api/v1/billing/' in path: filtered_tags = ['Billing'] elif '/account/' in path or '/api/v1/account/' in path: filtered_tags = ['Account'] elif '/automation/' in path or '/api/v1/automation/' in path: filtered_tags = ['Automation'] elif '/linker/' in path or '/api/v1/linker/' in path: filtered_tags = ['Linker'] elif '/optimizer/' in path or '/api/v1/optimizer/' in path: filtered_tags = ['Optimizer'] elif '/publisher/' in path or '/api/v1/publisher/' in path: filtered_tags = ['Publisher'] elif '/integration/' in path or '/api/v1/integration/' in path: filtered_tags = ['Integration'] elif '/admin/' in path or '/api/v1/admin/' in path: filtered_tags = ['Admin Billing'] operation['tags'] = filtered_tags # Now filter the tags list - keep only explicit tag definitions # The tags list contains tag definitions with 'name' and 'description' if 'tags' in result and isinstance(result['tags'], list): # Keep only tags that match our explicit tag names result['tags'] = [ tag for tag in result['tags'] if isinstance(tag, dict) and tag.get('name') in EXPLICIT_TAGS ] return result class JWTAuthenticationExtension(OpenApiAuthenticationExtension): """ OpenAPI extension for JWT Bearer Token authentication """ target_class = 'igny8_core.api.authentication.JWTAuthentication' name = 'JWTAuthentication' def get_security_definition(self, auto_schema): return build_bearer_security_scheme_object( header_name='Authorization', token_prefix='Bearer', bearer_format='JWT' ) class CSRFExemptSessionAuthenticationExtension(OpenApiAuthenticationExtension): """ OpenAPI extension for CSRF-exempt session authentication """ target_class = 'igny8_core.api.authentication.CSRFExemptSessionAuthentication' name = 'SessionAuthentication' def get_security_definition(self, auto_schema): return { 'type': 'apiKey', 'in': 'cookie', 'name': 'sessionid' }