Enhance API structure and documentation: Added new tags for Account, Integration, Automation, Linker, Optimizer, and Publisher; updated billing endpoints for admin and customer; improved API reference documentation; fixed endpoint paths in frontend services.

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-07 01:13:38 +00:00
parent dc9dba2c9e
commit 7a2b424237
15 changed files with 314 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ from django.contrib.auth import get_user_model
from django.db.models import Q, Count, Sum
from django.utils import timezone
from datetime import timedelta
from drf_spectacular.utils import extend_schema, extend_schema_view
from igny8_core.auth.models import Account
from igny8_core.business.billing.models import CreditTransaction
@@ -17,6 +18,10 @@ from igny8_core.business.billing.models import CreditTransaction
User = get_user_model()
@extend_schema_view(
retrieve=extend_schema(tags=['Account']),
partial_update=extend_schema(tags=['Account']),
)
class AccountSettingsViewSet(viewsets.ViewSet):
"""Account settings management"""
permission_classes = [IsAuthenticated]
@@ -77,6 +82,11 @@ class AccountSettingsViewSet(viewsets.ViewSet):
})
@extend_schema_view(
list=extend_schema(tags=['Account']),
create=extend_schema(tags=['Account']),
destroy=extend_schema(tags=['Account']),
)
class TeamManagementViewSet(viewsets.ViewSet):
"""Team members management"""
permission_classes = [IsAuthenticated]
@@ -166,6 +176,9 @@ class TeamManagementViewSet(viewsets.ViewSet):
)
@extend_schema_view(
overview=extend_schema(tags=['Account']),
)
class UsageAnalyticsViewSet(viewsets.ViewSet):
"""Usage analytics and statistics"""
permission_classes = [IsAuthenticated]

View File

@@ -8,7 +8,20 @@ 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'}
EXPLICIT_TAGS = {
'Authentication',
'Planner',
'Writer',
'System',
'Billing',
'Account',
'Automation',
'Linker',
'Optimizer',
'Publisher',
'Integration',
'Admin Billing',
}
def postprocess_schema_filter_tags(result, generator, request, public):
@@ -41,6 +54,20 @@ def postprocess_schema_filter_tags(result, generator, request, public):
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

View File

@@ -8,6 +8,7 @@ from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.shortcuts import get_object_or_404
from django.utils import timezone
from drf_spectacular.utils import extend_schema
from igny8_core.business.automation.models import AutomationConfig, AutomationRun
from igny8_core.business.automation.services import AutomationService
@@ -30,6 +31,7 @@ class AutomationViewSet(viewsets.ViewSet):
site = get_object_or_404(Site, id=site_id, account=request.user.account)
return site, None
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'])
def config(self, request):
"""
@@ -68,6 +70,7 @@ class AutomationViewSet(viewsets.ViewSet):
'next_run_at': config.next_run_at,
})
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['put'])
def update_config(self, request):
"""
@@ -142,6 +145,7 @@ class AutomationViewSet(viewsets.ViewSet):
'next_run_at': config.next_run_at,
})
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['post'])
def run_now(self, request):
"""
@@ -175,6 +179,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'])
def current_run(self, request):
"""
@@ -211,6 +216,7 @@ class AutomationViewSet(viewsets.ViewSet):
}
})
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['post'])
def pause(self, request):
"""
@@ -234,6 +240,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_404_NOT_FOUND
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['post'])
def resume(self, request):
"""
@@ -262,6 +269,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_404_NOT_FOUND
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'])
def history(self, request):
"""
@@ -291,6 +299,7 @@ class AutomationViewSet(viewsets.ViewSet):
]
})
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'])
def logs(self, request):
"""
@@ -323,6 +332,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_404_NOT_FOUND
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'])
def estimate(self, request):
"""
@@ -342,6 +352,7 @@ class AutomationViewSet(viewsets.ViewSet):
'sufficient': site.account.credits >= (estimated_credits * 1.2)
})
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'])
def pipeline_overview(self, request):
"""
@@ -504,6 +515,7 @@ class AutomationViewSet(viewsets.ViewSet):
]
})
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['get'], url_path='current_processing')
def current_processing(self, request):
"""
@@ -547,6 +559,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['post'], url_path='pause')
def pause_automation(self, request):
"""
@@ -596,6 +609,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['post'], url_path='resume')
def resume_automation(self, request):
"""
@@ -649,6 +663,7 @@ class AutomationViewSet(viewsets.ViewSet):
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@extend_schema(tags=['Automation'])
@action(detail=False, methods=['post'], url_path='cancel')
def cancel_automation(self, request):
"""

View File

@@ -11,14 +11,21 @@ from .views import (
AdminBillingViewSet,
AccountPaymentMethodViewSet,
)
from igny8_core.modules.billing.views import (
CreditBalanceViewSet,
CreditUsageViewSet,
)
router = DefaultRouter()
router.register(r'invoices', InvoiceViewSet, basename='invoice')
router.register(r'payments', PaymentViewSet, basename='payment')
router.register(r'credit-packages', CreditPackageViewSet, basename='credit-package')
router.register(r'transactions', CreditTransactionViewSet, basename='transaction')
router.register(r'admin', AdminBillingViewSet, basename='admin-billing')
router.register(r'payment-methods', AccountPaymentMethodViewSet, basename='payment-method')
# Canonical credits endpoints (unified billing)
router.register(r'credits/balance', CreditBalanceViewSet, basename='credit-balance')
router.register(r'credits/usage', CreditUsageViewSet, basename='credit-usage')
router.register(r'credits/transactions', CreditTransactionViewSet, basename='credit-transactions')
urlpatterns = [
# Country/config-driven available methods (legacy alias)

View File

@@ -9,6 +9,7 @@ from rest_framework.permissions import IsAuthenticated
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.db import models
from drf_spectacular.utils import extend_schema, extend_schema_view
from .models import (
Invoice,
@@ -366,6 +367,14 @@ class CreditTransactionViewSet(viewsets.ViewSet):
})
@extend_schema_view(
invoices=extend_schema(tags=['Admin Billing']),
payments=extend_schema(tags=['Admin Billing']),
pending_payments=extend_schema(tags=['Admin Billing']),
approve_payment=extend_schema(tags=['Admin Billing']),
reject_payment=extend_schema(tags=['Admin Billing']),
stats=extend_schema(tags=['Admin Billing']),
)
class AdminBillingViewSet(viewsets.ViewSet):
"""Admin billing management"""
permission_classes = [IsAuthenticated]

View File

@@ -1,6 +1,10 @@
from django.urls import path
from rest_framework.routers import DefaultRouter
from .views import AdminBillingViewSet
from igny8_core.business.billing.views import (
AdminBillingViewSet as BillingAdminViewSet,
)
router = DefaultRouter()
@@ -9,6 +13,12 @@ urlpatterns = [
path('users/', AdminBillingViewSet.as_view({'get': 'list_users'}), name='admin-users-list'),
path('users/<int:user_id>/adjust-credits/', AdminBillingViewSet.as_view({'post': 'adjust_credits'}), name='admin-adjust-credits'),
path('credit-costs/', AdminBillingViewSet.as_view({'get': 'list_credit_costs', 'post': 'update_credit_costs'}), name='admin-credit-costs'),
# Unified admin billing endpoints (alias legacy /billing/admin/* under /admin/billing/*)
path('billing/invoices/', BillingAdminViewSet.as_view({'get': 'invoices'}), name='admin-billing-invoices'),
path('billing/payments/', BillingAdminViewSet.as_view({'get': 'payments'}), name='admin-billing-payments'),
path('billing/pending_payments/', BillingAdminViewSet.as_view({'get': 'pending_payments'}), name='admin-billing-pending-payments'),
path('billing/<int:pk>/approve_payment/', BillingAdminViewSet.as_view({'post': 'approve_payment'}), name='admin-billing-approve-payment'),
path('billing/<int:pk>/reject_payment/', BillingAdminViewSet.as_view({'post': 'reject_payment'}), name='admin-billing-reject-payment'),
]
urlpatterns += router.urls

View File

@@ -20,6 +20,9 @@ urlpatterns = [
path('', include(router.urls)),
# User-facing billing overview
path('account_balance/', BillingOverviewViewSet.as_view({'get': 'account_balance'}), name='account-balance'),
# Canonical credit balance endpoint
path('credits/balance/', CreditBalanceViewSet.as_view({'get': 'list'}), name='credit-balance-canonical'),
# Explicit list endpoints
path('transactions/', CreditTransactionViewSet.as_view({'get': 'list'}), name='transactions'),
path('usage/', CreditUsageViewSet.as_view({'get': 'list'}), name='usage'),
# Admin billing endpoints

View File

@@ -26,7 +26,7 @@ from .exceptions import InsufficientCreditsError
@extend_schema_view(
list=extend_schema(tags=['Billing']),
list=extend_schema(tags=['Billing'], summary='Get credit balance'),
)
class CreditBalanceViewSet(viewsets.ViewSet):
"""
@@ -38,8 +38,7 @@ class CreditBalanceViewSet(viewsets.ViewSet):
throttle_scope = 'billing'
throttle_classes = [DebugScopedRateThrottle]
@action(detail=False, methods=['get'])
def balance(self, request):
def list(self, request):
"""Get current credit balance and usage"""
account = getattr(request, 'account', None)
if not account:
@@ -125,6 +124,7 @@ class CreditUsageViewSet(AccountModelViewSet):
return queryset.order_by('-created_at')
@extend_schema(tags=['Billing'], summary='Get usage summary')
@action(detail=False, methods=['get'])
def summary(self, request):
"""Get usage summary for date range"""
@@ -214,6 +214,7 @@ class CreditUsageViewSet(AccountModelViewSet):
serializer = UsageSummarySerializer(data)
return success_response(data=serializer.data, request=request)
@extend_schema(tags=['Billing'], summary='Get usage limits')
@action(detail=False, methods=['get'], url_path='limits', url_name='limits')
def limits(self, request):
"""
@@ -434,6 +435,13 @@ class BillingOverviewViewSet(viewsets.ViewSet):
return Response(data)
@extend_schema_view(
stats=extend_schema(tags=['Admin Billing'], summary='Admin billing stats'),
list_users=extend_schema(tags=['Admin Billing'], summary='List users with credit info'),
adjust_credits=extend_schema(tags=['Admin Billing'], summary='Adjust user credits'),
list_credit_costs=extend_schema(tags=['Admin Billing'], summary='List credit cost configurations'),
update_credit_costs=extend_schema(tags=['Admin Billing'], summary='Update credit cost configurations'),
)
class AdminBillingViewSet(viewsets.ViewSet):
"""Admin-only billing management API"""
permission_classes = [IsAuthenticatedAndActive, permissions.IsAdminUser]

View File

@@ -6,6 +6,7 @@ from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.utils import timezone
from drf_spectacular.utils import extend_schema, extend_schema_view
from igny8_core.api.base import SiteSectorModelViewSet
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
@@ -21,6 +22,14 @@ import logging
logger = logging.getLogger(__name__)
@extend_schema_view(
list=extend_schema(tags=['Integration']),
create=extend_schema(tags=['Integration']),
retrieve=extend_schema(tags=['Integration']),
update=extend_schema(tags=['Integration']),
partial_update=extend_schema(tags=['Integration']),
destroy=extend_schema(tags=['Integration']),
)
class IntegrationViewSet(SiteSectorModelViewSet):
"""
ViewSet for SiteIntegration model.
@@ -88,6 +97,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
return SiteIntegrationSerializer
@extend_schema(tags=['Integration'])
@action(detail=True, methods=['post'])
def test_connection(self, request, pk=None):
"""
@@ -118,6 +128,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
def allow_request(self, request, view):
return True
@extend_schema(tags=['Integration'])
@action(detail=False, methods=['post'], url_path='test-connection',
permission_classes=[AllowAny], throttle_classes=[NoThrottle])
def test_connection_collection(self, request):
@@ -221,6 +232,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
logger.info(f"[IntegrationViewSet] Deleted integration {integration.id} due to failed connection test")
return error_response(result.get('message', 'Connection test failed'), None, status.HTTP_400_BAD_REQUEST, request)
@extend_schema(tags=['Integration'])
@action(detail=True, methods=['post'])
def sync(self, request, pk=None):
"""
@@ -252,6 +264,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
response_status = status.HTTP_200_OK if result.get('success') else status.HTTP_400_BAD_REQUEST
return success_response(result, request=request, status_code=response_status)
@extend_schema(tags=['Integration'])
@action(detail=True, methods=['get'])
def sync_status(self, request, pk=None):
"""

View File

@@ -11,6 +11,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from drf_spectacular.utils import extend_schema, extend_schema_view
from igny8_core.api.base import SiteSectorModelViewSet
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
@@ -20,6 +21,14 @@ from igny8_core.business.publishing.models import PublishingRecord, DeploymentRe
from igny8_core.business.publishing.services.publisher_service import PublisherService
@extend_schema_view(
list=extend_schema(tags=['Publisher']),
create=extend_schema(tags=['Publisher']),
retrieve=extend_schema(tags=['Publisher']),
update=extend_schema(tags=['Publisher']),
partial_update=extend_schema(tags=['Publisher']),
destroy=extend_schema(tags=['Publisher']),
)
class PublishingRecordViewSet(SiteSectorModelViewSet):
"""
ViewSet for PublishingRecord model.
@@ -41,6 +50,14 @@ class PublishingRecordViewSet(SiteSectorModelViewSet):
return PublishingRecordSerializer
@extend_schema_view(
list=extend_schema(tags=['Publisher']),
create=extend_schema(tags=['Publisher']),
retrieve=extend_schema(tags=['Publisher']),
update=extend_schema(tags=['Publisher']),
partial_update=extend_schema(tags=['Publisher']),
destroy=extend_schema(tags=['Publisher']),
)
class DeploymentRecordViewSet(SiteSectorModelViewSet):
"""
ViewSet for DeploymentRecord model.
@@ -63,6 +80,7 @@ class DeploymentRecordViewSet(SiteSectorModelViewSet):
return DeploymentRecordSerializer
@extend_schema_view()
class PublisherViewSet(viewsets.ViewSet):
"""
Publisher actions for publishing content.
@@ -76,6 +94,7 @@ class PublisherViewSet(viewsets.ViewSet):
super().__init__(**kwargs)
self.publisher_service = PublisherService()
@extend_schema(tags=['Publisher'])
@action(detail=False, methods=['post'], url_path='publish')
def publish(self, request):
"""

View File

@@ -1580,12 +1580,12 @@ class ContentViewSet(SiteSectorModelViewSet):
@extend_schema_view(
list=extend_schema(tags=['Writer - Taxonomies']),
create=extend_schema(tags=['Writer - Taxonomies']),
retrieve=extend_schema(tags=['Writer - Taxonomies']),
update=extend_schema(tags=['Writer - Taxonomies']),
partial_update=extend_schema(tags=['Writer - Taxonomies']),
destroy=extend_schema(tags=['Writer - Taxonomies']),
list=extend_schema(tags=['Writer']),
create=extend_schema(tags=['Writer']),
retrieve=extend_schema(tags=['Writer']),
update=extend_schema(tags=['Writer']),
partial_update=extend_schema(tags=['Writer']),
destroy=extend_schema(tags=['Writer']),
)
class ContentTaxonomyViewSet(SiteSectorModelViewSet):
"""

View File

@@ -352,12 +352,32 @@ SPECTACULAR_SETTINGS = {
# Tag configuration - prevent auto-generation and use explicit tags
'TAGS': [
{'name': 'Authentication', 'description': 'User authentication and registration'},
{'name': 'Account', 'description': 'Account settings, team, and usage analytics'},
{'name': 'Integration', 'description': 'Site integrations and sync'},
{'name': 'System', 'description': 'Settings, prompts, and integrations'},
{'name': 'Admin Billing', 'description': 'Admin-only billing management'},
{'name': 'Billing', 'description': 'Credits, usage, and transactions'},
{'name': 'Planner', 'description': 'Keywords, clusters, and content ideas'},
{'name': 'Writer', 'description': 'Tasks, content, and images'},
{'name': 'System', 'description': 'Settings, prompts, and integrations'},
{'name': 'Billing', 'description': 'Credits, usage, and transactions'},
{'name': 'Automation', 'description': 'Automation configuration and runs'},
{'name': 'Linker', 'description': 'Internal linking operations'},
{'name': 'Optimizer', 'description': 'Content optimization operations'},
{'name': 'Publisher', 'description': 'Publishing records and deployments'},
],
'TAGS_ORDER': [
'Authentication',
'Account',
'Integration',
'System',
'Admin Billing',
'Billing',
'Planner',
'Writer',
'Automation',
'Linker',
'Optimizer',
'Publisher',
],
'TAGS_ORDER': ['Authentication', 'Planner', 'Writer', 'System', 'Billing'],
# Postprocessing hook to filter out auto-generated tags
'POSTPROCESSING_HOOKS': ['igny8_core.api.schema_extensions.postprocess_schema_filter_tags'],