""" 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'} 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: # 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'] 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' }